Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

NESTING THE ACTIVE SCHEDULER

135

(with iActive set to ETrue and a TRequestStatus indicating that it has completed).

9.5 Starting the Active Scheduler

Once an active scheduler has been created and installed, its event processing wait loop is started by a call to the static CActiveScheduler::Start() method. Application programmers do not have to worry about this, since the CONE framework takes care of managing the active scheduler. If you are writing server code, or a simple console application, you have to create and start the active scheduler for your server thread, which can be as simple as follows:

CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;

CleanupStack::PushL(scheduler);

CActiveScheduler::Install(scheduler);

The call to Start() enters the event processing loop and does not return until a corresponding call is made to CActiveScheduler::Stop(). Thus, before the active scheduler is started, there must be at least one asynchronous request issued, via an active object, so that the thread’s request semaphore is signaled and the call to User::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 to CActiveScheduler::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, so it should only be called by the main active object controlling the thread.

9.6 Nesting the Active Scheduler

I’ve already noted that an event-handling thread has a single active scheduler. However, it is possible, if unusual, to nest other calls to

CActiveScheduler::Start(), say within a RunL() event-handling method. The use of nested active scheduler loops is generally discouraged but can be useful if a call should appear to be synchronous, while actually being asynchronous (”pseudo-synchronous”). A good example is a RunL() event handler that requires completion of an asynchronous request to another active object in that thread. The RunL() call cannot be pre-empted, so it must instead create a nested wait loop by calling CActiveScheduler::Start(). This technique is used in modal Uikon ”waiting” dialogs.

136

ACTIVE OBJECTS UNDER THE HOOD

Each call to CActiveScheduler::Start() should be strictly matched by a corresponding call to CActiveScheduler::Stop() in an appropriate event handler. Before employing such a technique you must be careful to test your code thoroughly to ensure that the nesting is controlled under both normal and exceptional conditions. The use of nested active scheduler event-processing loops can introduce subtle bugs, particularly if more than one nested loop is used concurrently in the same thread. For example, if a pair of independent components both nest active scheduler loops, their calls to Start() and Stop() must be carefully interleaved if one component is to avoid stopping the loop of the other’s nested active scheduler.

The complexity that results from nesting active scheduler processing loops 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 the CActiveSchedulerWait class to provide nesting ”levels” that match active scheduler Stop() calls to the corresponding call to Start().

9.7 Extending the Active Scheduler

CActiveScheduler is a concrete class and can be used ”as is”, but it can also be subclassed. It defines two virtual functions which may be extended: Error() and WaitForAnyRequest().

By default, the WaitForAnyRequest() function simply calls User::WaitForAnyRequest(), but it may be extended, for example to perform some processing before or after the wait. If the function is re-implemented, it must either call the base class function or make a call to 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() method of the active object. If this method cannot handle the leave, it returns the leave code and the active scheduler passes it to its own Error() method. By default, this raises a panic (E32USER-CBASE 47), but it may be extended in a subclass to handle the error, for example by calling an error resolver to obtain the textual description of the error and displaying it to the user or logging it to file.

If your active object code is dependent upon particular specializations of the active scheduler, bear in mind that it will not be portable to run in other threads managed by more basic active schedulers. Furthermore, any additional code added to extend the active scheduler should be straightforward and you should avoid holding up event-handling in the entire thread by performing complex or slow processing.

CANCELLATION

137

9.8 Cancellation

Every request issued by an active object must complete exactly once. It can complete normally or complete early as a result of an error or a call to Cancel(). Let’s first examine what happens in a call to CActive::Cancel() and return to the other completion scenarios later.

CActive::Cancel() first determines if there is an outstanding request and, if so, it calls the DoCancel() method, a pure virtual function in CActive, implemented by the derived class (which should not override the non-virtual base class Cancel() method). DoCancel() does not need to check if there is an outstanding request; if there is no outstanding request, Cancel() does not call it. The encapsulated asynchronous service provider should provide a method to cancel an outstanding request and DoCancel() should call this method.

DoCancel() can include other processing, but it should not leave or allocate resources and it should not carry out any lengthy operations. This is because Cancel() is itself a synchronous function which does not 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 until the 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 of the active object rather than by the active scheduler.

Finally, Cancel() resets the iActive member of the active object to reflect that there is no longer an asynchronous request outstanding.

The Cancel() method of the CActive base class performs all this generic cancellation code. When implementing a derived active object class, you only need to implement DoCancel() to call the appropriate cancellation function on the asynchronous service provider and perform any cleanup necessary. You most certainly should not call User::WaitForRequest(), since this will upset the thread semaphore count. Internally, the active object must not call the protected DoCancel() method to cancel a request; it should call

CActive::Cancel(), which invokes DoCancel() and handles the resulting cancellation event.

When an active object request is cancelled by a call to Cancel(), the RunL() event handler does not run. This means that any post-cancellation cleanup must be performed in DoCancel() rather than in RunL().

138

ACTIVE OBJECTS UNDER THE HOOD

9.9 Request Completion

At this point, we can summarize the ways in which a request issued from an active object to an asynchronous service provider can complete:

The request is issued to the asynchronous service provider by the active object. Some time later, the asynchronous service provider calls User::RequestComplete() which generates a completion event and passes back a completion result. The active scheduler detects the completion event, resumes the thread and initiates event handling on the highest priority active object that has iActive set to ETrue and iStatus set to a value other than KRequestPending. This is a normal case, as described in the walkthrough above, although the completion result may not reflect a successful outcome.

The asynchronous request cannot begin, for example if invalid parameters are passed in or insufficient resources are available. The asynchronous service provider should define a function that neither leaves 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 passed into the request function.

The request is issued to the asynchronous service provider and Cancel() is called on the active object before the request has completed. The active object calls the appropriate cancellation function on the asynchronous service provider, which should terminate the request immediately. The asynchronous service provider should complete the request with KErrCancel as quickly as possible, because CActive::Cancel() blocks until completion occurs.

The request is issued to the asynchronous service provider and Cancel() is called on the active object some time after the request has completed. This occurs when the completion event has occurred but is yet to be processed by the active scheduler. The request appears to 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 Machines

An active object class can be used to implement a state machine to perform a series of actions in an appropriate sequence, without requiring

STATE MACHINES

139

client code to make multiple function calls or understand the logic of the 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 another location. The method takes the location of a data source, the destination and a TRequestStatus which is stored and used to indicate to the caller when the series of steps has completed. CStateMachine encapsulates an object of CServiceProvider class which provides the methods necessary to implement SendTranslatedData(). This class acts as an asynchronous service provider for the active object. Each asynchronous method takes a reference to a TRequestStatus object and has a corresponding Cancel() method.

The state machine class has an enumeration which represents the various stages required for SendTranslatedData() to succeed. It starts as CStateMachine::EIdle and must be in this state when the method is called (any other state indicates that a previous call to the method is currently outstanding). Having submitted a request by making the first logical call to CServiceProvider::GetData(), the method changes the iState member variable to reflect the new state (CStateMachine::EGet) and calls SetActive(). When it has finished, GetData() generates a completion event and the active scheduler, at some later point, invokes the CStateMachine::RunL() event handler. This is where the main logic of the state machine is implemented. You’ll see from the example that it first checks whether an error has occurred and, if so, it aborts the rest of the sequence and notifies the client. Otherwise, if the previous step was successful, the handler calls the next method in the sequence and changes its state accordingly, again calling SetActive(). This continues, driven by event completion and the RunL() event handler.

For clarity, in the example code below, I’ve only shown the implementation of functions which are directly relevant to the state machine:

// Provides the "step" functions class 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:

140

ACTIVE OBJECTS UNDER THE HOOD

CServiceProvider(){};

};

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 CActive virtual 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 MACHINES

141

HBufC* 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 later iTarget = aTarget.AllocL();

//To store retrieved data

iStorage = HBufC8::NewL(KStandardDataLen);

}

void CStateMachine::Cleanup()

{// Pointers are NULL-ed because this method is called outside // the destructor

iState = EIdle; delete iTarget; iTarget = NULL; delete iStorage; iStorage = NULL;

}

const TInt KStandardDataLen = 1024;

// Starts the state machine

void 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 later iClientStatus = &aStatus;

iClientStatus = KRequestPending;

TRAPD(r, InitializeL(aTarget); if (KErrNone!=r)

{// Allocation of iTarget of iStorage failed

Cleanup(); // Destroys successfully allocated member data User::RequestComplete(iClientStatus, r);

}

else

{

iService->GetData(aSource, iStorage, iStatus); iState = EGet;

SetActive();

}

}

142 ACTIVE OBJECTS UNDER THE HOOD

// The state machine is driven by this method void 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 machine User::RequestComplete(iClientStatus, iStatus.Int()); Cleanup();

}

else

{

if (EGet==iState)

{// Data was retrieved, now translate it synchronously TPtr8 ptr(iStorage->Des()); iService->TranslateData(ptr);

iState = ETranslate;

// Self completion – described later TRequestStatus* stat = &iStatus; User::RequestComplete(stat, r); SetActive();

}

else if (ETranslate==iState)

{// Data was translated, now send it asynchronously

TInt r = iService->SendData(*iTarget, *iStorage, iStatus); iState = ESend;

SetActive();

}

else

{// All done, notify the caller ASSERT(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 KErrCancel User::RequestComplete(iClientStatus, KErrCancel);

}

Cleanup();

}