Symbian's standard way of dealing with asynchronous requests uses a single threaded model. Events are serializated via "active objects", implemented by the Symbian class CActive and supporting classes. The m runtime follows this scheme: each m process is an active object; the m script runs in the process' CActive::RunL() function.
The asynchronous request status of the process object is passed to ExecuteL as TRequestStatus &status, allowing to issue asynchronous requests. But what happens if such a request completes? The answer is simple: ExecuteL() is called a again with exactly the same parameters.
Step by step, an asynchronous request is started and processed as follows:
All this sounds more complicated than it really is: most of the time, observing the following three items is sufficient when implementing an asynchronous function:
|
case WaitFunction: // only end waiting and return an alarm id if it expired if (status == EAlarmChangeEventTimerExpired) result.SetNumber(awaitedId); // otherwise start or continue waiting else { // wait for an alarm change notification session.NotifyChange(status, awaitedId); // signal to the runtime system that this call is asynchronous result.SetUncomplete(); & break; |
Since the alarm id passed to NotifyChange() must live between requests, we declare it as a (private) module class member variable:
|
TAlarmId awaitedId; |
Cancelling the NotifyChange request is simple:
|
void Cancel() { session.NotifyChangeCancel(); } |
If we compile[5] and install this module and try alarm.wait, we will get an exception like "[6]: Unknown error". What's wrong?
The problem is that NotifyChange() is somewhat peculiar: on successful completion, it does not assign KErrNone to the request status, but a positive TAlarmChangeEvent enumeration value. The m runtime system treats this as an exception. We therefore must override HandleError() to map these positive values to KErrNone:
|
TInt HandleError(TInt error) { // handle the non-zero positive returns from session.NotifyChange return error > 0 ? KErrNone : error; } |
After this addition, alarm.wait works as expected, except that our specification includes a millisecond timeout.
|
case WaitFunction: ... // if the wait included a timeout, add it if (paramCount > 0) runtime->AddTimeoutL(params[0].GetTimeoutL()); ... |
If we now call alarm.wait with a three second timeout, we will most likely get a timeout:
|
alarm.wait(3000) → ErrTimedOut thrown
|
We did not specify what should happen if the timeout expires. If we want alarm.wait to return null instead of throwing an exception, we must override HandleError() to map KErrTimedOut to KErrNone, and introduce a variable to remember it was a timeout the next time ExecuteL() is called:
|
TBool isTimeout; |
|
TInt HandleError(TInt error) { // handle timeout if (error == KErrTimedOut) { isTimeout = ETrue; return KErrNone; } // handle the non-zero positive returns from session.NotifyChange else return error > 0 ? KErrNone&n |
The implementation of WaitFunction also requires a small modification:
|
// if we ended with a timeout, reset flag and return default null if (isTimeout) isTimeout = EFalse; // only end waiting and return an alarm id if it expired else if (status == EAlarmChangeEventTimerExpired) ... |