Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 33
Текст из файла (страница 33)
// Omitted}_LIT(KExPanic, "CActiveExample");class CBackgroundRecalc : public CActive{public:... // NewL(), destructor etc are omitted for claritypublic:void PerformRecalculation(TRequestStatus& aStatus);protected:CBackgroundRecalc();void ConstructL();void Complete();virtual void RunL();virtual void DoCancel();private:CLongRunningCalculation* iCalc;TBool iMoreToDo;TRequestStatus* iCallerStatus; // To notify caller on completion};CBackgroundRecalc::CBackgroundRecalc(): CActive(EPriorityIdle) // Low priority task{ CActiveScheduler::Add(this); }LONG-RUNNING TASKS145// Issues a request to initiate a lengthy taskvoid CBackgroundRecalc::PerformRecalculation(TRequestStatus& aStatus){iCallerStatus = &aStatus;*iCallerStatus = KRequestPending;__ASSERT_ALWAYS(!IsActive(), User::Panic(KExPanic, KErrInUse));iMoreToDo = iCalc->StartTask(); // iCalc initializes the taskComplete();// Self-completion to generate an event}void CBackgroundRecalc::Complete(){// Generates an event on itself by completing on iStatusTRequestStatus* status = &iStatus;User::RequestComplete(status, KErrNone);SetActive();}// Performs the background task in incrementsvoid CBackgroundRecalc::RunL(){// Resubmit request for next increment of the task or stopif (!iMoreToDo){// Allow iCalc to cleanup any intermediate dataiCalc->EndTask();// Notify the callerUser::RequestComplete(iCallerStatus, iStatus.Int());}else{// Submit another request and self-complete to generate eventiMoreToDo = iCalc->DoTaskStep();Complete();}}void CBackgroundRecalc::DoCancel(){// Give iCalc a chance to perform cleanupif (iCalc)iCalc->EndTask();if (iCallerStatus) // Notify the callerUser::RequestComplete(iCallerStatus, KErrCancel);}If you are designing an API for a long-running task, to make it usefulwith this pattern, it is a good idea to provide methods which splitthe task into increments, as in the example above.
StartTask(),DoTaskStep() and EndTask() perform small, discrete chunks of thetask and can be called directly by the RunL() method of the low-priorityactive object. The API can then also be reused by code which implementslong-running tasks differently, since it will have no dependence on theactive object itself. Besides portability, distancing the active object modelmakes it more straightforward to focus on the implementation of thelong-running task itself.Of course, one disadvantage of this approach is that some tasks cannotbe broken down into short steps.
Another is that if you implement a146ACTIVE OBJECTS UNDER THE HOODnumber of low-priority active objects for long-running tasks in the samethread, you will need to work out how best to run them together andwrite the necessary low-priority scheduling code yourself.The use of a background thread for long-running tasks is fairly straightforward. The code for the task can be written without worrying aboutyielding the CPU, since the kernel schedules it when no higher-prioritythread needs to run, pre-empting it again when a more important threadneeds access to system resources.
However, as with all multi-threadedcode, any shared resources must be protected against illegal concurrentaccess by synchronization objects and, on Symbian OS, the overheadof running multiple threads is significantly higher than that for multipleactive objects running in a single thread. You should prefer low-priorityactive objects for long-running tasks, except for cases where the taskcannot be split into convenient increments.
The next chapter illustrateshow to use a separate thread to perform a long-running task which iswrapped with code for an active object.9.12Class CIdleCIdle derives from CActive and is a useful class which wraps theactive object basics such as implementing RunL() and DoCancel().The wrapper allows you to focus solely on implementing the codeto run the incremental task without worrying about the active objectcode.class CIdle : public CActive{public:IMPORT_C static CIdle* New(TInt aPriority);IMPORT_C static CIdle* NewL(TInt aPriority);IMPORT_C ∼CIdle();IMPORT_C void Start(TCallBack aCallBack);protected:IMPORT_C CIdle(TInt aPriority);IMPORT_C void RunL();IMPORT_C void DoCancel();protected:TCallBack iCallBack;};The CIdle object should be created with a low or idle priority andthe long-running task initiated by a call to Start(), passing a callbackfunction to perform an increment of the task.
The TCallback objectencapsulates a pointer to a callback function which takes an argumentof type TAny* and returns an integer. The callback function managesthe task increments and can be a local or a static member function. Itshould keep track of the task progress, returning ETrue if further steps areCLASS CIdle147necessary and EFalse when it is complete. In much the same way as theincremental task shown above, the RunL() event handler which calls theTCallback object is only called during idle time.
Furthermore, it willnot be pre-empted while it is running. As long as the callback functionindicates that further steps of the task are required, CIdle::RunL()resubmits requests by completing on its own iStatus object and callingSetActive().Here’s some example code for another background recalculation taskclass. In fact, I’ve slightly reworked the class from the example aboveto be driven by CIdle. You’ll notice that there’s no ”boilerplate” activeobject code required, unlike the code in the CBackgroundRecalcclass, because CIdle provides that functionality.class CLongRunningCalculation : public CBase{public:static CLongRunningCalculation* NewLC(TRequestStatus& aStatus);protected:static TInt TaskStep(TAny* aLongCalc); // Callback functionprotected:void StartTask();// Initialization before starting the taskTBool DoTaskStep(); // Does a step of the taskvoid EndTask();// Destroys intermediate dataprotected:CLongRunningCalculation(TRequestStatus& aStatus);private:...TBool iMoreToDo; // Flag tracks the progress of the task// To notify the caller when the calc is completeTRequestStatus* iCallerStatus;};CLongRunningCalculation* CLongRunningCalculation::NewLC(TRequestStatus&aStatus){CLongRunningCalculation* me = new (ELeave)CLongRunningCalculation(aStatus);CleanupStack::PushL(me); // ...
2nd phase construction code omittedreturn (me);}CLongRunningCalculation::CLongRunningCalculation(TRequestStatus&aStatus): iCallerStatus(&aStatus) {}TBool CLongRunningCalculation::DoTaskStep(){// Does a short task step, returning ETrue if it has more to do... // Omitted}void CLongRunningCalculation::StartTask(){// Prepares the taskiMoreToDo = ETrue;}148ACTIVE OBJECTS UNDER THE HOOD// Error handling omittedvoid CLongRunningCalculation::EndTask(){// Performs cleanup when the task has completedASSERT(!iMoreToDo);...User::RequestComplete(iCallerStatus, KErrNone);}TInt CLongRunningCalculation::TaskStep(TAny* aLongCalc){ASSERT(aLongCalc);CLongRunningCalculation* longCalc =static_cast<CLongRunningCalculation*>(aLongCalc);if (!longCalc->iMoreToDo)longCalc->EndTask();elselongCalc->iMoreToDo = longCalc->DoTaskStep();return (longCalc->iMoreToDo);}Code which uses the idle object will look something like the followingexample, which creates the CIdle object and a CLongRunningCalculation object that drives the task:CIdle* idle = CIdle::NewL(EPriorityIdle);CleanupStack::PushL(idle);// Create the object that runs the task, passing in a TRequestStatus&CLongRunningCalculation* longCalc =CLongRunningCalculation::NewLC(iStatus);TCallBack callback(CLongRunningCalculation::TaskStep, longCalc);idle->Start(callback);...
// Completion of the task completes iStatus9.13Class CPeriodicCPeriodic is another useful CActive-derived class for running incremental task steps. It uses a timer object to generate regular timer events,handling a single step of the task for each timer event. CPeriodic isuseful for performing regular task steps such as flashing a cursor or forcontrolling time-outs.Like CIdle, CPeriodic is initialized with a priority value and thetask is initiated by a call to Start(), passing settings for the timer as wellas a callback to perform increments of the task. When the timer periodelapses, the CPeriodic object generates an event that is detected bythe active scheduler.
When there are no active objects of higher priorityrequiring event handling, the active scheduler calls the RunL() eventCOMMON MISTAKES149handler of the CPeriodic object, which in turn calls its task-processingcallback function. Thus, the callback may not be as exactly regular asthe periodic value passed to the Start() request. If the timer completesbut other, higher-priority, active objects have completed events, theyare processed first. Alternatively, the RunL() method of another activeobject may be running when the timer elapses and a running active objectcannot be pre-empted even if it has a lower priority than the CPeriodicobject.The response of signal and callback processing can be improved byensuring that all active objects in the thread perform short, efficientRunL() methods and that the CPeriodic object has a higher prioritythan other active objects.9.14 Common MistakesI’ve described the dos and don’ts of active objects fairly comprehensivelyin this chapter and the previous one.
The most commonly encounteredproblem when writing active object code is the infamous ”stray signal”panic (E32USER-CBASE 46), which occurs when the active schedulerreceives a completion event but cannot find an active object to handle it(i.e.
one which is currently active and has a completed iStatus result,indicated by a value other than KRequestPending). Stray signals canarise for the following reasons:• CActiveScheduler::Add() was not called when the active objectwas constructed• SetActive() was not called following the submission of a requestto the asynchronous service provider• the asynchronous service provider completed the TRequestStatusof an active object more than once – either because of a programmingerror in the asynchronous service provider (for example, when analready-completed request is cancelled) or because more than onerequest was submitted simultaneously on the same active object.If a stray signal occurs from one of your active objects, it is worthchecking against each of these.Over the course of Chapters 8 and 9, I have described the cooperativemultitasking nature of active objects, which means that an active object’sRunL() event handler cannot be pre-empted by any other in that thread,except by using nested active scheduler loops, which are strongly discouraged.
In consequence, when using active objects for event handlingin, for example, a UI thread, their event-handler methods must be keptshort to keep the UI responsive.150ACTIVE OBJECTS UNDER THE HOODNo active object should have a monopoly on the active scheduler thatprevents other active objects from handling events. Active objects shouldbe ”cooperative”.