Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 27
Текст из файла (страница 27)
At thispoint, the service has simply registered the request. By returning without processing the request, the client is decoupled from the processingof the service request, which may take an indeterminate time or mayfail.When dealing with a service that handles requests asynchronously youcould halt the execution of your thread and wait until the service handlesyour request and sends you the result. However, this defeats the wholepurpose of the service providing an asynchronous API and instead youneed to find some way to continue to treat the period of waiting and thesubsequent handling of the response as just another task that should bescheduled alongside all the other tasks you need to execute. Hence youneed to multitask.
The problem is to do so without using a multithreadedsolution to avoid the complications and RAM overhead described in theintroduction to this chapter.ExampleApplications that wish to make a phone call can do so by accessing theTelephony subsystem of Symbian OS through the CTelephony API. ThisAPI is asynchronous due to the relatively high latency of establishinga connection.Of course, the main application thread could block while waitingfor the call to be connected but this would render any UI controlsunresponsive and would probably frustrate the end user.
A better strategyis to adopt a cooperative multitasking approach executed within a singlethread.SolutionThe Symbian OS active object framework provides developers with a consistent, uniform, and general-purpose model for managing asynchronousbehavior without resorting to multithreading.An active object is used by some client object to encapsulate the taskof making an asynchronous service request and handling the eventualrequest completion all within a single thread but without blocking untilthe request completes. This pattern builds on Request Completion (seepage 104) to provide a more usable, higher-level API that shields developers from the trickiness of multitasking, by moving complexity out ofsight into the internals of the operating system.For many purposes, active objects are as effective as threads.
Unlessthere is an explicit reason not to use them, Symbian OS active objects areusually the simplest solution for managing asynchronictity.ACTIVE OBJECTS135CActiveCActiveScheduler++++Install (CActiveScheduler*)Add(CActive*)Start()Stop()+ iStatus: TRequestStatus− iLink: TPriQueLink1..* +###CClientCancel()RunL()RunError(Tlnt)DoCancel()CMyActive1..*+ IssueAsynRequest()Figure 5.1 Structure of the Active Objects patternStructureThere are two principle classes in the active object framework4 in additionto your concrete active objects (see Figure 5.1).• Active scheduler represented by CActiveSchedulerThis class is responsible for managing and scheduling a list of activeobjects within a single thread.
Each active scheduler maintains itsown list of runnable active objects, and chooses the next object torun based on its active object priority or, when several active objectshave the same priority, in First-In–First-Out (FIFO) order.Note that any code running in the thread can add active objectsto the active scheduler which includes code provided by anothervendor that is included as a DLL. An example of this is a servicedesigned according to Client-Thread Service (see page 171). This canbe an issue because it is more difficult to arrange the active objectsfrom different vendors to cooperate well at run time. Since the activescheduler runs a non-pre-emptive scheduling scheme there is no wayto recover if a single, high-priority active object prevents all otheractive objects from running.• Active object represented by CActiveThis defines the abstract base class from which all concrete activeobjects are derived.
The first thing you need to do is define theasynchronous request to be encapsulated by an active object andprovide a function that issues the request. This function would usethe TRequestStatus it owns and pass it into the service providing4 Definedin epoc32\include\e32base.h.136COOPERATIVE MULTITASKINGthe asynchronous API. It is this TRequestStatus object that iscompleted when the request has been completed.5The CActive class uses virtual functions and the Template Methodpattern [Gamma et al., 1994] to define the responsibilities of concreteactive objects.
As a minimum, you have to provide an implementation of RunL(), to handle the completion of your asynchronousrequest, and DoCancel(), to provide a way to tell the asynchronousservice provider to attempt to stop processing your request if it is stilloutstanding.6If required, you can also implement RunError() to provide yourown specific error handling since this function is called if a Leaveoccurs within your RunL().DynamicsSetting up the Active SchedulerWhen a thread is created, an active scheduler must be created andinstalled for it.
Before it can be started, at least one active object withan outstanding request must be added to the active scheduler before it isstarted. An active scheduler is then normally not stopped until the threadexits. The steps required to set up the active scheduler for a thread with asingle active object are shown in Figure 5.2.Normal Request CompletionFigure 5.3 shows a simple request that completes normally.
Within theRunL(), the active object should first check to see if its request completedwith an error and handle the success or failure of the request appropriately.At a system-wide level, any running active object can be pre-emptedby the kernel’s normal thread scheduling mechanism, since an activeobject is run within a thread like any other executable object. However,it is important to note that, locally, all the active objects run by a givenactive scheduler multitask cooperatively; once an active object is startedrunning, it runs to completion before the next ready-to-run active objectcan be started by that scheduler.You need to be aware of this to ensure that the active objects youcreate run efficiently as well as to avoid unintended side effects such asone active object blocking others within the thread from running. Henceit is important that the RunL() completes quickly, for instance, within100 ms,7 so that your thread remains responsive to further events fromthe end user or elsewhere.5 SeeRequest Completion (page 104).DoCancel() is only called by the framework when it knows there is a requestoutstanding.7 As this is roughly the threshold at which a delay is detectable by an end user.
See alsothe Long Running Active Object variation of this pattern.6ACTIVE OBJECTS137CClientAsynchronousService ProviderActive ObjectFrameworkconstructor()Install()CMyActiveNewL()Add(this)IssueAsyncRequest()AsyncRequest(iStatus)SetActive()Start()Figure 5.2Dynamics of the Active Objects pattern (setting up the active scheduler)CClientActive ObjectFrameworkCMyActiveIssueAsyncRequestL()AsynchronousService ProviderAsyncRequest(iStatus)SetActive()Always call this aftermaking anasynchronous requestto tell the frameworkthat you are waiting fora request completion.ProcessRequest()The active schedulerselects the highestpriority active objectwith a completedrequest to run.RequestCompleted(iStatus)RunL()HandleCompletionL()Figure 5.3 Dynamics of the Active Objects pattern (normal request completion)138COOPERATIVE MULTITASKINGCanceling an Asynchronous RequestThe primary purpose of CActive::Cancel() is to tell the asynchronous service provider that you have no more interest in the request.As the implementer of an active object, you need to ensure thishappens by calling the appropriate Cancel() function given by theasynchronous service provider from within your DoCancel() function(see Figure 5.4).8 The active object framework then blocks until the service provider completes the request.
However, this does not result in aRunL() being called since you’ve expressed no interest in the requestby calling Cancel(). It also avoids any problem with a badly writtenRunL() firing off another request just when you wanted to destroy theactive object.CClientActive ObjectFrameworkCMyActiveAsynchronousService ProviderIssueAsynRequest()AsyncRequest(iStatus)SetActive()Cancel()DoCancel()CancelAsyncRequest()WaitForRequest(iStatus)RequestCompleted(iStatus)Dynamics of the Active Objects pattern (canceling an asynchronous request)Figure 5.4ImplementationActive objects are thoroughly documented in the Symbian DeveloperLibrary as well as in many books from Symbian Press such as [Stichbury,2004] and [Harrison and Shackman, 2007].
However, the followingsummarizes the most important points to remember when creating andusing this pattern.8Your DoCancel()is only called if there is a request outstanding.ACTIVE OBJECTS139Setting up the Active SchedulerAn active scheduler is normally one of the first things created when athread runs, often in a local function called directly from the E32Main()thread entry point such as the following:static void RunThreadL(){// Create and install the active schedulerCActiveScheduler* s = new(ELeave) CActiveScheduler;CleanupStack::PushL(s);CActiveScheduler::Install(s);// Prepare the initial list of active objectsCMyActive* myActive = CMyActive::NewLC(); // Adds myActive to themyActive->IssueAsyncRequest();// scheduler...// Ready to runCActiveScheduler::Start(); // Doesn’t return until it is explicitly// stopped// Clean up the scheduler and myActiveCleanupStack::PopAndDestroy(2);}Note that you do not need to do this for applications as the applicationframework creates an active scheduler for you.Creating an Active ObjectYour active object will look something like this:class CMyActive : public CActive{public:static CMyActive* NewL();∼CMyActive();void IssueAsyncRequest();private:CMyActive();// from CActivevoid RunL();TInt RunError(TInt aError);void DoCancel();};When implementing the active object, your first task in the constructor is to set up the priority used by it.
Exactly what value youuse depends on the other active objects it is cooperating with. However, the priority should be chosen with care. The list available to140COOPERATIVE MULTITASKINGchoose from is given by the CActive::TPriority enum defined inepoc32\include\e32base.h. Remember that an object with a highpriority will be chosen to run ahead of others, which means that it ispossible for one object to starve others of processor time if it is madeactive again at the end of its RunL().You should also register the current object with the active scheduler:CMyActive::CMyActive(): CActive(CActive::EPriorityStandard){CActiveScheduler::Add(this);}When implementing the function that issues the asynchronous servicerequest you should check to see if your object is already active. This checkis important.
If an active object succeeds in making a new request beforethe currently outstanding request has completed, a stray signal will result9and cause a system panic, since the first request won’t be able to completecorrectly. It is appropriate to panic if the function is called incorrectly asper Fail Fast (see page 17) since it indicates a fault in the calling code.However, if an error occurs which cannot be handled simply bypanicking and terminating the thread then you should not Leave from thefunction, as per Escalate Errors (see page 32).