Prev: User Permissions
Asynchronous Request Handling
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:
- ExecuteL() is called as usual.
- A function starts an asynchronous request, passing the status reference to the requesting function.
- To signal to the m runtime system that an asynchronous request was started, ExecuteL() must return with an "uncomplete" result, which is obtained via Runtime::Value::SetUncomplete().
- The runtime system deals with the other issues of active objects, calls SetActive(), suspends the m process, and so on.
- Once the request completes (with or without error), the m runtime system continues the m process.
- If the request completed with any result other than KErrNone, the module's HandleError() function is called to give it an opportunity to handle the error as a normal situation and return KErrNone.
- If the request result is or was mapped by HandleError() to KErrNone, ExecuteL() is called again, with the original request result in status. If ExecuteL() starts another asynchronous request, it must return an "uncomplete" result again. Otherwise, it should return the function result.
- If the request result was any other code, the module's Cancel() function is called, and the result is converted into an m exception, going through GetErrorMessage() to check for a module specific exception text.
Cancel() is not only called in the case of errors, but also if the process is stopped or closed by the user, if the
m application is exited, or if a timeout expires. As a consequence, you
must override
NativeModule::Cancel() (which does nothing by default) if any of your functions start asynchronous requests.
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:
- Have your function issue the asynchronous request, passing status, and return a value which was SetUncomplete().
- Maintain an internal state so you know whether an asynchronous function is to be started or completes each time ExecuteL() is called.
- Override Cancel() to cancel the request and to reset the internal state.
Alarm Server Example, Continued
The function
alarm.wait is asynchronous by its very nature. The alarm server provides function
RASCliSession::NotifyChange() to asynchronously wait for a change event in the alarm server. The event we are interested in is
EAlarmChangeEventTimerExpired. All other events should immediately start another wait for change notification. In this case, the request status serves as our internal state which allows us to know whether to start or complete a request:
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:
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.
Timeouts
Adding a timeout to an asynchronous function is simple: the runtime system maintains an internal timer and offers a single function to start it:
- void AddTimeoutL(TTimeIntervalMicroSeconds32 timeout);
Adds a microsecond timeout to an asynchronous request. If the timeout is reached, the native module's Cancel() function will be called, and the request completes with KErrTimedOut. If timeout is negative, no timeout is added.
Use Runtime::Value::GetTimeoutL() to convert a millisecond number to a microsecond timeout value.
If you do not want your function to throw an exception if the timeout expires, you must handle
KErrTimedOut in
HandleError().
Alarm Server Example, Continued
If
alarm.wait was called with a parameter, we call
AddTimeoutL(), passing the parameter value:
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:
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 : error;
}
|
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)
...
|
Next: Native Objects© 2004-2010 airbit AG, CH-8008 Zürich
Document AB-M-NMI-869