Websites Navigation: Airbit | Shop | m-shell.net
Languages: EN | DE

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:

  1. ExecuteL() is called as usual.
  2. A function starts an asynchronous request, passing the status reference to the requesting function.
  3. 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().
  4. The runtime system deals with the other issues of active objects, calls SetActive(), suspends the m process, and so on.
  5. Once the request completes (with or without error), the m runtime system continues the m process.
  6. 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.
  7. 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.
  8. 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:

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.

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:

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 : 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)
    ...


© 2004-2010 airbit AG, CH-8008 Zürich
Document AB-M-NMI-869
mShell Home  > Documentation  > Manuals