Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 32
Текст из файла (страница 32)
Eachasynchronous method takes a reference to a TRequestStatus objectand has a corresponding Cancel() method.The state machine class has an enumeration which represents thevarious stages required for SendTranslatedData() to succeed. Itstarts as CStateMachine::EIdle and must be in this state whenthe method is called (any other state indicates that a previous call tothe method is currently outstanding). Having submitted a request bymaking the first logical call to CServiceProvider::GetData(),the method changes the iState member variable to reflect the newstate (CStateMachine::EGet) and calls SetActive().
When it hasfinished, GetData() generates a completion event and the active scheduler, at some later point, invokes the CStateMachine::RunL() eventhandler. This is where the main logic of the state machine is implemented. You’ll see from the example that it first checks whether an errorhas occurred and, if so, it aborts the rest of the sequence and notifies theclient. Otherwise, if the previous step was successful, the handler callsthe next method in the sequence and changes its state accordingly, againcalling SetActive(). This continues, driven by event completion andthe RunL() event handler.For clarity, in the example code below, I’ve only shown the implementation of functions which are directly relevant to the statemachine:// Provides the "step" functionsclass CServiceProvider : public CBase{public:static CServiceProvider* NewL();∼CServiceProvider() {};public:void GetData(const TDesC& aSource, HBufC8*& aData,TRequestStatus& aStatus);void CancelGetData();TInt TranslateData(TDes8& aData);void SendData(const TDesC& aTarget, const TDesC8& aData,TRequestStatus& aStatus);void CancelSendData();protected:140ACTIVE OBJECTS UNDER THE HOODCServiceProvider(){};};void CServiceProvider::GetData(const TDesC& aSource, HBufC8*& aData,TRequestStatus& aStatus){aStatus = KRequestPending;// Retrieves data from aSource using the asynchronous overload of// RFile::Read() and writing to aData (re-allocating it if// necessary).
aStatus is completed by the file server when the// read has finished...}void CServiceProvider::CancelGetData() {...}TInt CServiceProvider::TranslateData(TDes8& aData){// Synchronously translates aData & writes into same descriptor...return (translationResult);}void CServiceProvider::SendData(const TDesC& aTarget, const TDesC8&aData, TRequestStatus& aStatus){aStatus = KRequestPending;// Writes data to aTarget using the asynchronous overload of// RFile::Write(), which completes aStatus...}void CServiceProvider::CancelSendData() {...}class CStateMachine : public CActive{public:∼CStateMachine();static CStateMachine* NewLC();void SendTranslatedData(const TDesC& aSource, const TDesC& aTarget,TRequestStatus&);protected:enum TState { EIdle, EGet, ETranslate, ESend};protected:CStateMachine();void InitializeL(const TDesC& aTarget);void Cleanup();protected:virtual void DoCancel(); // Inherited from CActivevirtual void RunL();// The following base class method is not overridden because// RunL() cannot leave// virtual TInt RunError(TInt aError);private:CServiceProvider* iService;TState iState;private:STATE MACHINESHBufC* iTarget;HBufC8* iStorage;TRequestStatus* iClientStatus;};CStateMachine::CStateMachine(): CActive(EPriorityStandard) {CActiveScheduler::Add(this);}CStateMachine::∼CStateMachine(){Cancel();Cleanup();}void CStateMachine::InitializeL(const TDesC& aTarget){// Store this to pass to CServiceProvider lateriTarget = aTarget.AllocL();// To store retrieved dataiStorage = HBufC8::NewL(KStandardDataLen);}void CStateMachine::Cleanup(){// Pointers are NULL-ed because this method is called outside// the destructoriState = EIdle;delete iTarget;iTarget = NULL;delete iStorage;iStorage = NULL;}const TInt KStandardDataLen = 1024;// Starts the state machinevoid CStateMachine::SendTranslatedData(const TDesC& aSource,const TDesC& aTarget, TRequestStatus& aStatus){__ASSERT_ALWAYS(!IsActive(), User::Panic(KExPanic, KErrInUse));ASSERT(EIdle==iState);// Store the client request status to complete lateriClientStatus = &aStatus;iClientStatus = KRequestPending;TRAPD(r, InitializeL(aTarget);if (KErrNone!=r){// Allocation of iTarget of iStorage failedCleanup(); // Destroys successfully allocated member dataUser::RequestComplete(iClientStatus, r);}else{iService->GetData(aSource, iStorage, iStatus);iState = EGet;SetActive();}}141142ACTIVE OBJECTS UNDER THE HOOD// The state machine is driven by this methodvoid CStateMachine::RunL(){// Inspects result of completion and iState// and submits next request (if required)ASSERT(EIdle!=iState);if (KErrNone!=iStatus.Int()){// An error - notify the client and stop the state machineUser::RequestComplete(iClientStatus, iStatus.Int());Cleanup();}else{if (EGet==iState){// Data was retrieved, now translate it synchronouslyTPtr8 ptr(iStorage->Des());iService->TranslateData(ptr);iState = ETranslate;// Self completion – described laterTRequestStatus* stat = &iStatus;User::RequestComplete(stat, r);SetActive();}else if (ETranslate==iState){// Data was translated, now send it asynchronouslyTInt r = iService->SendData(*iTarget, *iStorage, iStatus);iState = ESend;SetActive();}else{// All done, notify the callerASSERT(ESend==iState);User::RequestComplete(iClientStatus, iStatus.Int());Cleanup();}}}void CStateMachine::DoCancel(){if (iService){if (CStateMachine::EGet = =iState){iService->CancelGetData();}else if (CStateMachine::ESend = =iState){iService->CancelSendData();}}if (iClientStatus){// Complete the caller with KErrCancelUser::RequestComplete(iClientStatus, KErrCancel);}Cleanup();}LONG-RUNNING TASKS143In effect, CStateMachine maintains a series of outstanding requeststo the service provider in RunL(), rather than making a single call.This example is quite straightforward because there are only three activestates, but the code logic here can potentially be far more complex.The DoCancel() method of CStateMachine must also have somestate-related logic so that the correct method on CServiceProvider iscancelled.
The states and transitions are illustrated in Figure 9.2.Send TranslatedData()Call CServiceProvider::GetData()1. EIdle2. EGetCancel() orRunError()Cancel(),RunError() ornormal completion4. ESendRunL() callsCServiceProvider::TranslateData()RunError()(self completes)3. ETranslateRunL() callsCServiceProvider::SendData()Figure 9.2 Internal states of CStateMachineIn this example the service provider functions called by the statemachine are a mixture of synchronous (TranslateData()) and asynchronous (GetData() and SendData()) functions.
The synchronousmethod uses self-completion to simulate an asynchronous completionevent, which is discussed further in the following section.9.11 Long-Running TasksBesides encapsulating asynchronous service providers, active objects canalso be used to implement long-running tasks which would otherwiseneed to run in a lower-priority background thread.To be suitable, the task must be divisible into multiple short increments, for example, preparing data for printing, performing backgroundrecalculations and compacting the database.
The increments are performed in the event handler of the active object. For this reason, theymust be short enough for event handling in the thread to continue to beresponsive, because RunL() cannot be pre-empted once it is running.The active object should be assigned a low priority such asCActive::TPriority::EPriorityIdle (=-100), which determines that a task increment only runs when there are no other events to144ACTIVE OBJECTS UNDER THE HOODhandle, i.e. in idle time.
If the task consists of a number of different steps,the active object must track the progress as series of states, implementingit using a state machine as described above.The active object drives the task by generating its own events to invokeits event handler. Instead of calling an asynchronous service provider,it completes itself by calling User::RequestComplete() on its owniStatus object and calls SetActive() on itself so the active schedulercalls its event handler. In this way it continues to resubmit requests untilthe entire task is complete. A typical example is shown in the samplecode, where I’ve shown all the relevant methods in the class declarationsbut only the implementations relevant to this discussion.
I’ve also omittederror handling for clarity:// This class has no dependence on the active object frameworkclass CLongRunningCalculation : public CBase{public:static CLongRunningCalculation* NewL();TBool StartTask(); // Initialization before starting the taskTBool DoTaskStep(); // Performs a short task stepvoid EndTask();// Destroys intermediate data...};TBool CLongRunningCalculation::DoTaskStep(){// Do a short task step, returning// ETrue if there is more of the task to do// EFalse if the task is complete...