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

WORKING WITH ACTIVE OBJECTS

115

8.3 Working with Active Objects

Let’s move on now to consider in more detail how active objects work, and how to use them. A typical Symbian OS application or server consists of a single event-handling thread running a scheduler (the ”active scheduler”) which coordinates one or more active objects. Each active object requests an asynchronous service and handles the resulting completion event some time after the request. It also provides a way to cancel an outstanding request and may provide error handling for exceptional conditions.

An active object class must derive from class CActive, which is defined in e32base.h (shown here with only the relevant methods, for clarity). The next chapter discusses details of the base class further.

class CActive : public CBase

{

public:

enum TPriority

{

EPriorityIdle=-100,

EPriorityLow=-20,

EPriorityStandard=0,

EPriorityUserInput=10,

EPriorityHigh=20,

};

public:

IMPORT_C CActive(); IMPORT_C void Cancel();

...

IMPORT_C void SetPriority(TInt aPriority); inline TBool IsActive() const;

...

protected:

IMPORT_C CActive(TInt aPriority); IMPORT_C void SetActive(); virtual void DoCancel() =0; virtual void RunL() =0;

IMPORT_C virtual TInt RunError(TInt aError); public:

TRequestStatus iStatus;

...

};

Construction

Like threads, active objects have a priority value to determine how they are scheduled. Classes deriving from CActive must call the protected constructor of the base class, passing in a parameter to set the priority of the active object.

When the asynchronous service associated with the active object completes, it generates an event. The active scheduler detects events, determines which active object is associated with each event, and calls

116

EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS

the appropriate active object to handle the event. I’ll describe this in more detail in the later section on event handling. While an active object is handling an event, it cannot be pre-empted3 until the event handler function has returned back to the active scheduler.

It is quite possible that a number of events may complete before control returns to the scheduler. The scheduler must resolve which active object gets to run next; it does this by ordering the active objects using their priority values. If multiple events have occurred before control returns to the scheduler, they are handled sequentially in order of the highest active object priority, rather than in order of completion. Otherwise, an event of low priority that completed just before a more important one would supplant the higher-priority event for an undefined period, depending on how much code it executed to run to completion.

A set of priority values are defined in the TPriority enumeration of class CActive. For the purposes of this chapter, and in general, you should use the priority value EPriorityStandard (=0) unless you have good reason to do otherwise. The next chapter discusses the factors you should consider when setting the priority of your active objects on construction or by making additional calls to CActive::SetPriority().

As part of construction, the active object code should call a static function on the active scheduler, CActiveScheduler::Add(). This will add the object to a list maintained by the active scheduler of event-handling active objects on that thread.

An active object typically owns an object to which it issues requests that complete asynchronously, generating an event, such as a timer object of type RTimer. This object is generally known as an asynchronous service provider and it may need to be initialized as part of construction. Of course, if the initialization can fail, you should perform it as part of the second-phase construction, as described in Chapter 4.

Submitting Requests

An active object class supplies public methods for callers to initiate requests. These will submit requests to the asynchronous service provider associated with the active object, using a well-established pattern, as follows:

1.Request methods should check that there is no request already submitted before attempting to submit another. Each active object

3 While this is true under most circumstances, it is possible to nest a separate active scheduler within the event handler of an active object, and use the nested active scheduler to receive other active objects’ events. This is used in Uikon for modal ”waiting” dialogs, as discussed further in the next chapter. However, this technique can cause complications and should be used with caution. For the purposes of discussion in this chapter, you should simply consider that all active object event handling is non-preemptive.

WORKING WITH ACTIVE OBJECTS

117

can only ever have one outstanding request, for reasons I’ll discuss further in the next chapter. Depending on the implementation, the code may:

panic if a request has already been issued, if this scenario could only occur because of a programming error

refuse to submit another request (if it is legitimate to attempt to make more than one request)

cancel the outstanding request and submit the new one.

2.The active object should then issue the request to the service provider, passing in its iStatus member variable as the TRequestStatus& parameter.

3.If the request is submitted successfully, the request method then calls the SetActive() method of the CActive base class, to indicate to the active scheduler that a request has been submitted and is currently outstanding. This call is not made until after the request has been submitted.

Event Handling

Each active object class must implement the pure virtual RunL() member method of the CActive base class. When a completion event occurs from the associated asynchronous service provider and the active scheduler selects the active object to handle the event, it calls RunL() on the active object.4 The function has a slightly misleading name because the asynchronous function has already run. Perhaps a clearer description would be HandleEventL() or HandleCompletionL().

Typical implementations of RunL() determine whether the asynchronous request succeeded by inspecting the completion code in the TRequestStatus object (iStatus) of the active object, a 32-bit integer value. Depending on the result, RunL() usually either issues another request or notifies other objects in the system of the event’s completion; however, the degree of complexity of code in RunL() can vary considerably. Whatever it does, once RunL() is executing, it cannot be pre-empted by other active objects’ event handlers. For this reason, the code should complete as quickly as possible so that other events can be handled without delay.

Figure 8.1 illustrates the basic sequence of actions performed when an active object submits a request to an asynchronous service provider that later completes, generating an event which is handled by RunL().

4 Advanced Windows programmers will recognize the pattern of a message loop and message dispatch which drives a Win32 application. On Symbian OS, the active scheduler takes the place of the Windows message loop and the RunL() function of an active object acts as a message handler.

118

EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS

Active Object

(1) Issues request passing iStatus

(3) Calls SetActive() on itself

(5) Active Scheduler calls RunL() on the active object to handle the event

RunL() resubmits another request or stops the Active Scheduler

RunL() cannot be preempted

Asynchronous Service

Provider

(2) Sets iStatus=KRequestPending and starts the service

(4) Service completes. Service provider uses

RequestComplete() to notify the Active Scheduler and post a completion result

PROCESS OR

THREAD

BOUNDARY

Figure 8.1 Process of submitting a request to an asynchronous service provider, which generates an event on completion

Each active object class must implement the pure virtual RunL() member method of the CActive base class to handle completion events.

Cancellation

The active object must be able to cancel any outstanding asynchronous requests it has issued, for example, if the application thread in which it

WORKING WITH ACTIVE OBJECTS

119

is running is about to terminate. The CActive base class implements a Cancel() method which calls the pure virtual DoCancel() method (which the derived active object class must implement) and waits for the request’s early completion. Any implementation of DoCancel() should call the appropriate cancellation method on the asynchronous service provider. DoCancel() can also include other processing, but should not leave or allocate resources and should not carry out any lengthy operations. It’s a good rule to restrict the method to cancellation and any necessary cleanup associated with the request, rather than implementing any sophisticated functionality. This is because a destructor should call Cancel(), as described in later, and may have already cleaned up resources that DoCancel() may require.

It isn’t necessary to check whether a request is outstanding for the active object before calling Cancel(), because it is safe to do so even if it isn’t currently active. The next chapter discusses in detail the semantics of canceling active objects.

Error Handling

From Symbian OS v6.0 onwards, the CActive class provides a virtual RunError() method which the active scheduler calls if a leave occurs in the RunL() method of the active object. The method takes the leave code as a parameter and returns an error code to indicate whether the leave has been handled. The default implementation does not handle the leave and simply returns the leave code passed to it. If the active object can handle any leaves occurring in RunL(), it should do so by overriding the default implementation and returning KErrNone to indicate that the leave has been handled.

If RunError() returns a value other than KErrNone, indicating that the leave has yet to be dealt with, the active scheduler calls its Error() function to handle it. The active scheduler does not have any contextual information about the active object with which to perform error handling. For this reason, it is generally preferable to manage error recovery within the RunError() method of the associated active object, where more context is usually available.

Destruction

The destructor of a CActive-derived class should always call Cancel() to terminate any outstanding requests as part of cleanup code. This should ideally be done before any other resources owned by the active object are destroyed, in case they are used by the service provider or the DoCancel() method. The destructor code should, as usual, free all