Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 27
Текст из файла (страница 27)
If multiple events have occurred before control returns tothe scheduler, they are handled sequentially in order of the highest activeobject priority, rather than in order of completion. Otherwise, an eventof low priority that completed just before a more important one wouldsupplant the higher-priority event for an undefined period, depending onhow much code it executed to run to completion.A set of priority values are defined in the TPriority enumerationof class CActive. For the purposes of this chapter, and in general, youshould use the priority value EPriorityStandard (=0) unless you havegood reason to do otherwise.
The next chapter discusses the factors youshould 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 staticfunction on the active scheduler, CActiveScheduler::Add(). Thiswill add the object to a list maintained by the active scheduler ofevent-handling active objects on that thread.An active object typically owns an object to which it issues requests thatcomplete asynchronously, generating an event, such as a timer object oftype RTimer. This object is generally known as an asynchronous serviceprovider and it may need to be initialized as part of construction.
Ofcourse, if the initialization can fail, you should perform it as part of thesecond-phase construction, as described in Chapter 4.Submitting RequestsAn active object class supplies public methods for callers to initiate requests. These will submit requests to the asynchronous serviceprovider associated with the active object, using a well-established pattern, as follows:1.Request methods should check that there is no request alreadysubmitted before attempting to submit another. Each active object3While this is true under most circumstances, it is possible to nest a separate activescheduler within the event handler of an active object, and use the nested active schedulerto 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 complicationsand should be used with caution. For the purposes of discussion in this chapter, you shouldsimply consider that all active object event handling is non-preemptive.WORKING WITH ACTIVE OBJECTS117can only ever have one outstanding request, for reasons I’ll discussfurther in the next chapter. Depending on the implementation, thecode may:• panic if a request has already been issued, if this scenario couldonly occur because of a programming error• refuse to submit another request (if it is legitimate to attempt tomake 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¶meter.3. If the request is submitted successfully, the request method then callsthe SetActive() method of the CActive base class, to indicateto the active scheduler that a request has been submitted and iscurrently outstanding. This call is not made until after the request hasbeen submitted.Event HandlingEach active object class must implement the pure virtual RunL() membermethod of the CActive base class. When a completion event occurs fromthe associated asynchronous service provider and the active schedulerselects the active object to handle the event, it calls RunL() on theactive object.4 The function has a slightly misleading name because theasynchronous function has already run.
Perhaps a clearer descriptionwould be HandleEventL() or HandleCompletionL().Typical implementations of RunL() determine whether the asynchronous request succeeded by inspecting the completion code in theTRequestStatus object (iStatus) of the active object, a 32-bit integer value. Depending on the result, RunL() usually either issues anotherrequest or notifies other objects in the system of the event’s completion; however, the degree of complexity of code in RunL() can varyconsiderably. Whatever it does, once RunL() is executing, it cannot bepre-empted by other active objects’ event handlers.
For this reason, thecode should complete as quickly as possible so that other events can behandled without delay.Figure 8.1 illustrates the basic sequence of actions performed when anactive object submits a request to an asynchronous service provider thatlater completes, generating an event which is handled by RunL().4Advanced Windows programmers will recognize the pattern of a message loop andmessage dispatch which drives a Win32 application. On Symbian OS, the active schedulertakes the place of the Windows message loop and the RunL() function of an active objectacts as a message handler.118EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTSActive Object(1) Issues requestpassing iStatusAsynchronous ServiceProvider(2) Sets iStatus=KRequestPendingand starts the service(3) Calls SetActive()on itself(5) Active Schedulercalls RunL() on theactive object to handlethe event(4) Service completes.Service provider usesRequestComplete() tonotify the Active Schedulerand post a completionresultRunL() resubmitsanother request orstops the ActiveSchedulerRunL() cannot be preemptedPROCESS ORTHREADBOUNDARYFigure 8.1 Process of submitting a request to an asynchronous service provider, which generates an eventon completionEach active object class must implement the pure virtual RunL()member method of the CActive base class to handle completion events.CancellationThe active object must be able to cancel any outstanding asynchronousrequests it has issued, for example, if the application thread in which itWORKING WITH ACTIVE OBJECTS119is running is about to terminate.
The CActive base class implements aCancel() method which calls the pure virtual DoCancel() method(which the derived active object class must implement) and waits for therequest’s early completion. Any implementation of DoCancel() shouldcall the appropriate cancellation method on the asynchronous serviceprovider.
DoCancel() can also include other processing, but shouldnot leave or allocate resources and should not carry out any lengthyoperations. It’s a good rule to restrict the method to cancellation and anynecessary cleanup associated with the request, rather than implementingany sophisticated functionality. This is because a destructor should callCancel(), as described in later, and may have already cleaned upresources that DoCancel() may require.It isn’t necessary to check whether a request is outstanding for theactive object before calling Cancel(), because it is safe to do so even ifit isn’t currently active.
The next chapter discusses in detail the semanticsof canceling active objects.Error HandlingFrom Symbian OS v6.0 onwards, the CActive class provides a virtualRunError() method which the active scheduler calls if a leave occursin the RunL() method of the active object. The method takes the leavecode as a parameter and returns an error code to indicate whether theleave has been handled. The default implementation does not handle theleave and simply returns the leave code passed to it. If the active objectcan handle any leaves occurring in RunL(), it should do so by overridingthe default implementation and returning KErrNone to indicate that theleave has been handled.If RunError() returns a value other than KErrNone, indicating thatthe leave has yet to be dealt with, the active scheduler calls its Error()function to handle it.
The active scheduler does not have any contextualinformation about the active object with which to perform error handling.For this reason, it is generally preferable to manage error recovery withinthe RunError() method of the associated active object, where morecontext is usually available.DestructionThe destructor of a CActive-derived class should always call Cancel()to terminate any outstanding requests as part of cleanup code.
Thisshould ideally be done before any other resources owned by the activeobject are destroyed, in case they are used by the service provider orthe DoCancel() method. The destructor code should, as usual, free all120EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTSresources owned by the object, including any handle to the asynchronousservice provider.The CActive base class destructor is virtual and its implementationchecks that the active object is not currently active. It panics if any requestis outstanding, that is, if Cancel() has not been called. This catches anyprogramming errors which could lead to the situation where a requestcompletes after the active object to handle it has been destroyed.
Thiswould otherwise result in a ”stray signal”, described further in Chapter 9,where the active scheduler cannot locate an active object to handlethe event.Having verified that the active object has no issued requests outstanding, the CActive destructor removes the active object from theactive scheduler.The destructor of a CActive-derived class should always callCancel() to terminate any outstanding requests before cleanupproceeds.8.4 Example CodeThe example below illustrates the use of an active object class to wrapan asynchronous service, in this case a timer provided by the RTimerservice. In fact, Symbian OS already supplies an abstract active objectclass, CTimer, which wraps RTimer and can be derived from, specifying the action required when the timer expires. However, I’ve createda new class, CExampleTimer, because it’s a straightforward way ofillustrating active objects. Figure 8.2 illustrates the classes involved andtheir relationship with the active scheduler.When the timer expires, the RunL() event handler checks the activeobject’s iStatus result and leaves if it contains a value other thanKErrNone so the RunError() method can handle the problem.
Inthis case, the error handling is very simple: the error returned from therequest is logged to file. This could have been performed in the RunL()method, but I’ve separated it into RunError() to illustrate how to usethe active object framework to split error handling from the main logicof the event handler. If no error occurred, the RunL() event handlerlogs the timer completion to debug output using RDebug::Print()(described in Chapter 17) and resubmits the timer request with the storedinterval value.
In effect, once the timer request has started, it continues toexpire and be resubmitted until it is stopped by a call to the Cancel()method on the active object.EXAMPLE CODECActiveRunL()DoCancel()RunError()...iActiveiStatus121CActiveSchedulern1Add()...CExampleTimerNewL()AfterRunL()DoCancel()RunError()...iTimerRTimerFigure 8.2 CExampleTimer and its relationship with RTimer, CActive and CActiveScheduler_LIT(KExampleTimerPanic, "CExampleTimer");class CExampleTimer : public CActive{public:∼CExampleTimer();static CExampleTimer* NewL();void After(TTimeIntervalMicroSeconds32& aInterval);protected:CExampleTimer();void ConstructL();protected:virtual void RunL();// Inherited from CActivevirtual void DoCancel();virtual TInt RunError(TInt aError);private:RTimer iTimer;TTimeIntervalMicroSeconds32 iInterval;};CExampleTimer::CExampleTimer(): CActive(EPriorityStandard) { CActiveScheduler::Add(this); }void CExampleTimer::ConstructL()122EVENT-DRIVEN MULTITASKING USING ACTIVE OBJECTS{// Create the asynchronous service providerUser::LeaveIfError(iTimer.CreateLocal());}CExampleTimer* CExampleTimer::NewL(){...} // Code omitted for clarityCExampleTimer::∼CExampleTimer(){Cancel();iTimer.Close();}void CExampleTimer::After(TTimeIntervalMicroSeconds32& aInterval){// Only allow one timer request to be submitted at a time// Caller must call Cancel() before submitting another__ASSERT_ALWAYS(!IsActive(), User::Panic(KExampleTimerPanic,KErrInUse));iInterval = aInterval;iTimer.After(iStatus,aInterval); // Set the RTimerSetActive(); // Mark this object active}void CExampleTimer::RunL(){// If an error occurred (admittedly unlikely)// deal with the problem in RunError()User::LeaveIfError(iStatus.Int());// Otherwise, log the timer completion and resubmit the timer_LIT(KTimerExpired, "Timer Expired\n");RDebug::Print(KTimerExpired); // See Chapter 17 for more detailsiTimer.After(iStatus, iInterval);SetActive();}void CExampleTimer::DoCancel(){// Cancel the timeriTimer.Cancel();}TInt CExampleTimer::RunError(TInt aError){// Called if RunL() leaves, aError contains the leave code_LIT(KErrorLog, "Timer error %d");RDebug::Print(KErrorLog, aError); // Logs the errorreturn (KErrNone);// Error has been handled}The example is simplistic but it demonstrates the use of an active objectto wrap an asynchronous function, in this case, timer completion.