Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

9

Active Objects under the Hood

Do not hack me as you did my Lord Russell

The last words of the Duke of Monmouth (1649–1685) addressed to his executioner

The previous chapter introduced active objects and described the basics, such as how to use them, how to derive a simple active object class and why active objects are used by Symbian OS as a lightweight alternative to threads for event-driven multitasking. This chapter considers active objects in more detail and discusses some commonly used active object idioms.

First of all, let’s examine in detail the responsibilities of active objects, asynchronous service providers and the active scheduler and walk through how they fit together. The previous chapter made the following main points:

Symbian OS event-handling is usually managed in one thread, which runs a single active scheduler

the active scheduler holds a set of active objects for that thread, each of which encapsulates an associated asynchronous service provider

each thread has an associated request semaphore; when an asynchronous function completes, it generates an event by calling RequestComplete() on the requesting thread, which signals its semaphore and is detected by the active scheduler

the active scheduler calls the RunL() event handler method of the active object associated with the completion event.

In the previous chapter, I used example code for an active object wrapper over an RTimer to illustrate the main points. You may find it useful to refer to that example throughout this chapter.

128

ACTIVE OBJECTS UNDER THE HOOD

9.1 Active Object Basics

All active objects must derive from class CActive. On construction, each active object is assigned a priority value, which is set through a call to the base class constructor. In its constructor, the active object must also be added to the active scheduler through a call to CActiveScheduler::Add(). The active scheduler maintains a doubly-linked list of the active objects added to it, ordered by priority. When an active object is added to the active scheduler, it is added to that list in the appropriate position, according to its priority value.

As I described in Chapter 8, the active object encapsulates asynchronous functions (those that return immediately and complete at some later stage rather than returning only when the request has completed). On Symbian OS, asynchronous functions can be identified as those taking a TRequestStatus reference parameter into which the request completion status is posted. An object implementing such methods is usually known as an asynchronous service provider.

A typical active object class provides public ”request issuer” methods for its clients to submit asynchronous requests. These pass on the requests to the encapsulated asynchronous service provider, passing by reference the iStatus member variable of the CActive class as the TRequestStatus parameter. Having issued the request, the issuer method must call CActive::SetActive() to set the iActive member to indicate that there is an outstanding request.

The service provider must set the value of the incoming TRequestStatus to KRequestPending (=0x80000001) before acting on the request. Upon completion, if the service provider is in the same thread as the requester, it calls User::RequestComplete(), passing the TRequestStatus and a completion result, typically one of the standard errors such as KErrNone or KErrNotFound, to indicate the success or otherwise of the request. User::RequestComplete() sets the value of TRequestStatus and generates a completion event in the requesting thread by signaling the thread’s request semaphore. If the asynchronous service provider and the requester are in separate threads, the service provider must use an RThread object, representing a handle to the requesting thread, to complete the request. It should call

RThread::RequestComplete() to post the completion code and notify the request semaphore.

While the request is outstanding, the requesting thread runs in the active scheduler’s event processing loop. When it is not handling completion events, the active scheduler suspends the thread by calling User::WaitForAnyRequest(), which waits on a signal to the thread’s request semaphore. When the asynchronous service provider completes a request, it signals the semaphore of the requesting thread as described above, and the active scheduler determines which active

ACTIVE OBJECT BASICS

129

object should handle the completed request. It uses its priority-ordered list of active objects, inspecting each one in turn to determine whether it has a request outstanding. It does so by checking the iActive flag; if the object does indeed have an outstanding request, it then inspects its TRequestStatus member variable to see if it is set to a value other than KRequestPending. If so, this indicates that the active object is associated with a request that has completed and that its event handler code should be called.

Having found a suitable active object, the active scheduler clears the active object’s iActive flag and calls its RunL() event handler. This method handles the event and may, for example, resubmit a request or generate an event on another object in the system. While this method is running, other events may be generated but RunL() is not pre-empted – it runs to completion before the active scheduler resumes control and determines whether any other requests have completed.

Once the RunL() call has finished, the active scheduler re-enters the event processing wait loop by issuing another User::WaitForAnyRequest() call. This checks the request semaphore and either suspends the thread (if no other requests have completed in the meantime) or returns immediately (if the semaphore indicates that other events were generated while the previous event handler was running) so the scheduler can repeat active object lookup and event handling.

Here’s some pseudo-code which represents the basic actions of the active scheduler’s event processing loop.

EventProcessingLoop()

{

//Suspend the thread until an event occurs

User::WaitForAnyRequest();

//Thread wakes when the request semaphore is signaled

//Inspect each active object added to the scheduler,

//in order of decreasing priority

//Call the event handler of the first which is active & completed

FOREVER

{

//Get the next active object in the priority queue

if (activeObject->IsActive())

&& (activeObject->iStatus!=KRequestPending)

{// Found an active object ready to handle an event

//Reset the iActive status to indicate it is not active

activeObject->iActive = EFalse;

//Call the active object’s event handler in a TRAP

TRAPD(r, activeObject->RunL()); if (KErrNone!=r)

{// event handler left, call RunError() on active object r = activeObject->RunError();

if (KErrNone!=r) //

RunError() didn’t handle the error,

Error(r);

//

call CActiveScheduler::Error()

}

break; // Event handled. Break out of lookup loop & resume

130

ACTIVE OBJECTS UNDER THE HOOD

}

} // End of FOREVER loop

}

If a single request has completed on the thread in the interim, the active scheduler performs lookup and calls the appropriate event handler on that active object. If more than one request has completed in that time, the active scheduler calls the event handler for the highest priority active object. It follows that, if multiple events are generated in close succession while another event is being handled, those events may not be handled in the sequence in which they occurred because the active object search list is ordered by priority to support responsive event-handling.

Normally, active object code should be designed so the priority does not matter, otherwise the system can become rather delicate and be thrown off balance by minor changes or additional active objects on the thread. However, to be responsive, say for user input, it is sometimes necessary to use a higher priority value. Long-running, incremental tasks, on the other hand, should have a lower priority than standard since they are designed to use idle processor time (as I’ll describe later in this chapter).

It’s important to understand that the priority value is only an indication of the order in which the active scheduler performs lookup and eventhandling when multiple events have completed. In contrast to the priority values of threads used by the kernel scheduler, it does not represent an ability to pre-empt other active objects. Thus, if you assign a particular active object a very high priority, and it completes while a lower-priority active object is handling an event, no pre-emption occurs. The RunL() of the lower-priority object runs to completion, regardless of the fact that it is ”holding up” the handler for the higher-priority object.

On Symbian OS, you cannot use active object priorities to achieve a guaranteed response time; for this you must use the pre-emptive scheduling associated with threads1, which is described in Chapter 10.

If you have a large number of active objects in a single thread which complete often, they ”compete” for their event handler to be run by the active scheduler. If some of the active objects have high priorities and receive frequent completion events, those with lower priorities wait indefinitely until the active scheduler can call their RunL() methods. In effect, it’s possible to ”hang” lower-priority active objects by adding them to an active scheduler upon which a number of high-priority active objects are completing.

1 The new hard real-time kernel in Symbian OS 8.0 can commit to a particular response time. On earlier versions of Symbian OS, the kernel has soft real-time capabilities and cannot make such guarantees.