Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 31
Текст из файла (страница 31)
Thus, before the active scheduler is started,there must be at least one asynchronous request issued, via an activeobject, so that the thread’s request semaphore is signaled and the call toUser::WaitForAnyRequest() completes. If no request is outstanding, the thread simply enters the wait loop and sleeps indefinitely.As you would expect, the active scheduler is stopped by a call toCActiveScheduler::Stop(). When that enclosing function returns,the outstanding call to CActiveScheduler::Start() also returns.Stopping the active scheduler breaks off event handling in the thread, soit should only be called by the main active object controlling the thread.9.6 Nesting the Active SchedulerI’ve already noted that an event-handling thread has a single activescheduler.
However, it is possible, if unusual, to nest other calls toCActiveScheduler::Start(), say within a RunL() event-handlingmethod. The use of nested active scheduler loops is generally discouraged but can be useful if a call should appear to be synchronous, whileactually being asynchronous (”pseudo-synchronous”).
A good exampleis a RunL() event handler that requires completion of an asynchronousrequest to another active object in that thread. The RunL() call cannotbe pre-empted, so it must instead create a nested wait loop by callingCActiveScheduler::Start(). This technique is used in modalUikon ”waiting” dialogs.136ACTIVE OBJECTS UNDER THE HOODEach call to CActiveScheduler::Start() should be strictlymatched by a corresponding call to CActiveScheduler::Stop()in an appropriate event handler.
Before employing such a technique youmust be careful to test your code thoroughly to ensure that the nestingis controlled under both normal and exceptional conditions. The useof nested active scheduler event-processing loops can introduce subtlebugs, particularly if more than one nested loop is used concurrently inthe same thread. For example, if a pair of independent components bothnest active scheduler loops, their calls to Start() and Stop() must becarefully interleaved if one component is to avoid stopping the loop ofthe other’s nested active scheduler.The complexity that results from nesting active scheduler processingloops means that Symbian does not recommend this technique. However,where the use of nested active scheduler loops is absolutely unavoidable, releases of Symbian OS from v7.0s onwards have introduced theCActiveSchedulerWait class to provide nesting ”levels” that matchactive scheduler Stop() calls to the corresponding call to Start().9.7 Extending the Active SchedulerCActiveScheduler is a concrete class and can be used ”as is”, butit can also be subclassed.
It defines two virtual functions which may beextended: Error() and WaitForAnyRequest().By default, the WaitForAnyRequest() function simply callsUser::WaitForAnyRequest(), but it may be extended, for exampleto perform some processing before or after the wait.
If the function isre-implemented, it must either call the base class function or make a callto User::WaitForAnyRequest() directly.I described earlier how if a leave occurs in a RunL() event handler,the active scheduler passes the leave code to the RunError() methodof the active object. If this method cannot handle the leave, it returnsthe leave code and the active scheduler passes it to its own Error()method. By default, this raises a panic (E32USER-CBASE 47), but it maybe extended in a subclass to handle the error, for example by calling anerror resolver to obtain the textual description of the error and displayingit to the user or logging it to file.If your active object code is dependent upon particular specializationsof the active scheduler, bear in mind that it will not be portable to runin other threads managed by more basic active schedulers.
Furthermore,any additional code added to extend the active scheduler should bestraightforward and you should avoid holding up event-handling in theentire thread by performing complex or slow processing.CANCELLATION1379.8 CancellationEvery request issued by an active object must complete exactly once.It can complete normally or complete early as a result of an erroror a call to Cancel(). Let’s first examine what happens in a call toCActive::Cancel() and return to the other completion scenarios later.CActive::Cancel() first determines if there is an outstandingrequest and, if so, it calls the DoCancel() method, a pure virtual function in CActive, implemented by the derived class (whichshould not override the non-virtual base class Cancel() method).DoCancel() does not need to check if there is an outstanding request; ifthere is no outstanding request, Cancel() does not call it.
The encapsulated asynchronous service provider should provide a method to cancelan outstanding request and DoCancel() should call this method.DoCancel() can include other processing, but it should not leaveor allocate resources and it should not carry out any lengthy operations.This is because Cancel() is itself a synchronous function which doesnot return until both DoCancel() has returned and the original asynchronous request has completed.
That is, having called DoCancel(),CActive::Cancel() then calls User::WaitForRequest(), passing in a reference to its iStatus member variable. It is blocked untilthe asynchronous service provider posts a result (KErrCancel) into it,which should happen immediately, as described above.The cancellation event is thus handled by the Cancel() method ofthe active object rather than by the active scheduler.Finally, Cancel() resets the iActive member of the active objectto reflect that there is no longer an asynchronous request outstanding.The Cancel() method of the CActive base class performs allthis generic cancellation code.
When implementing a derived activeobject class, you only need to implement DoCancel() to call theappropriate cancellation function on the asynchronous service providerand perform any cleanup necessary. You most certainly should notcall User::WaitForRequest(), since this will upset the threadsemaphore count. Internally, the active object must not call the protected DoCancel() method to cancel a request; it should callCActive::Cancel(), which invokes DoCancel() and handles theresulting cancellation event.When an active object request is cancelled by a call toCancel(), the RunL() event handler does not run.
This means thatany post-cancellation cleanup must be performed in DoCancel()rather than in RunL().138ACTIVE OBJECTS UNDER THE HOOD9.9 Request CompletionAt this point, we can summarize the ways in which a request issuedfrom an active object to an asynchronous service provider can complete:• The request is issued to the asynchronous service provider by theactive object. Some time later, the asynchronous service provider callsUser::RequestComplete() which generates a completion eventand passes back a completion result. The active scheduler detectsthe completion event, resumes the thread and initiates event handlingon the highest priority active object that has iActive set to ETrueand iStatus set to a value other than KRequestPending. This isa normal case, as described in the walkthrough above, although thecompletion result may not reflect a successful outcome.• The asynchronous request cannot begin, for example if invalidparameters are passed in or insufficient resources are available.
Theasynchronous service provider should define a function that neitherleaves nor returns an error code (it should typically return void). Thus,under these circumstances, the request should complete immediately,posting an appropriate error into the TRequestStatus object passedinto the request function.• The request is issued to the asynchronous service provider andCancel() is called on the active object before the request hascompleted. The active object calls the appropriate cancellation function on the asynchronous service provider, which should terminate therequest immediately.
The asynchronous service provider should complete the request with KErrCancel as quickly as possible, becauseCActive::Cancel() blocks until completion occurs.• The request is issued to the asynchronous service provider andCancel() is called on the active object some time after the requesthas completed. This occurs when the completion event has occurredbut is yet to be processed by the active scheduler. The request appearsto be outstanding to the active object framework, if not to the asynchronous service provider, which simply ignores the cancellation call.CActive::Cancel() discards the normal completion result.9.10State MachinesAn active object class can be used to implement a state machine to perform a series of actions in an appropriate sequence, without requiringSTATE MACHINES139client code to make multiple function calls or understand the logic ofthe sequence.
The example below is of an active object class, CStateMachine, which has a single request method SendTranslatedData().This retrieves the data, converts it in some way and sends it to anotherlocation. The method takes the location of a data source, the destination and a TRequestStatus which is stored and used to indicate tothe caller when the series of steps has completed. CStateMachineencapsulates an object of CServiceProvider class which providesthe methods necessary to implement SendTranslatedData(). Thisclass acts as an asynchronous service provider for the active object.