Introduction to active objects

An active object encapsulates an asynchronous request and the behaviour that is required when the request completes.

When do I need to use an active object?

Symbian servers often provide both synchronous and asynchronous functions as part of their client side API. Synchronous functions return to the caller once the operation is complete, often indicating success or failure by means of a return value. In contrast asynchronous functions return to the caller as soon as the request is initiated and the completion of the request is signalled some time later. Asynchronous functions in Symbian always take a reference to a TRequestStatus as one of the function arguments.

It is possible to call an asynchronous function without using an active object. This is illustrated in the following code fragment, with error handling omitted for clarity:

// Create a user-side handle to a timer object.
RTimer aTimer;

// Instantiate the underlying kernel-side object.
aTimer.CreateLocal();

// Set the timer to expire after a delay, passing in a
// TRequestStatus. This will automatically be set to 
// KRequestPending until the timer completes.
TRequestStatus timerStatus;
aTimer.After(timerStatus, 3000000);

// Do some other work while the timer has not completed.
// Note, polling is not recommended.
while(timerStatus == KRequestPending) 
  {
  ...      
  }

// When the TRequestStatus has a value other than
// KRequestPending, the timer has completed. KErrNone
// indicates successful completion of the request.
if (timerStatus == KErrNone)	 
  {
  _LIT(KOut, "Timer expired\n");
  console->Write(KOut);
  }

// Clean up.
aTimer.Close();

In the code above we repeatedly check the value of the TRequestStatus to find out if the asynchronous request has completed. This approach is not efficient, and does not scale well if you want to make several concurrent asynchronous requests.

It is also possible to block until an asynchronous request has completed. You do this by calling User::WaitForRequest(TRequestStatus&) passing in the TRequestStatus associated with the request. The function does not return until the request has completed. User::WaitForRequest(TRequestStatus&) is sometimes used in test code, but it is never appropriate to block the main user interface thread in this way. Doing so can lead to an unresponsive interface and difficult to find bugs.

Making an asynchronous request using an active object

Using active objects allows you to manage asynchronous requests without resorting to polling or blocking the requesting thread. Instead you receive a callback when the asynchronous request completes. The callback function is invoked by the active scheduler, so you must use active objects together with an active scheduler. There is a single active scheduler per thread that manages all the active objects in the thread.

The figure below illustrates the basic sequence of actions performed when an active object submits a request to an asynchronous service provider. The asynchronous service provider is the object that carries out the request in another thread and generates an event on completion, resulting in the active scheduler invoking RunL() on the active object that made the request.

Figure: A request to an asynchronous service provider

How the active scheduler calls RunL() on the correct active object

When you use one or more active objects the management of the application thread is carried out by the active scheduler's event processing loop. The event-processing loop starts when you call CActiveScheduler::Start() and does not return until a call is made to CActiveScheduler::Stop() from an active object's RunL() method.

When it is not handling other completion events, the active scheduler suspends a thread by calling User::WaitForAnyRequest(), which waits for a signal to the thread’s request semaphore. You do not need to create this semaphore, it is already a member of the application thread. When an asynchronous service provider has finished with a request, it writes a completion code to the TRequestStatus associated with the request and indicates completion by signaling the semaphore.

When a signal is received and the thread is next scheduled, the active scheduler determines which active object should handle it. It checks the priority-ordered list of active objects for those with outstanding requests (these have previously called CActive::SetActive() immediately after making a request). If an active object has an outstanding request, the active scheduler checks its iStatus member variable to see if it is set to a value other than KRequestPending. If so, the active scheduler calls its RunL() event handler.

Once the RunL() call has finished, the active scheduler re-enters the event processing wait loop by issuing another User::WaitForAnyRequest() call. This checks the thread’s request semaphore and either suspends the thread (if no other events need handling) or returns immediately to look up the next active object on which to call RunL().

The following pseudo-code illustrates the event-processing loop:
void CActiveScheduler::EventProcessingLoop()    
{    
// Suspend the thread until an event occurs. 
User::WaitForAnyRequest();

// Thread wakes when the request semaphore is signaled.  
// Inspect each active object added to the scheduler,  
// in order of decreasing priority.   
// Call the event handler of the first which is active and completed.
FOREVER
  {
  // Get the next active object in the priority queue.
  if ( (activeObject->IsActive()) && (activeObject->iStatus!=KRequestPending) )
     {      
     // Found an active object ready to handle an event.
     // Reset the iActive status to indicate it is not active.
     activeObject->iActive = EFalse;

     // Call the active object’s event handler in a TRAP.
     TRAPD(r, activeObject->RunL());
     if (KErrNone!=r)
        {   
        // Event handler left, call RunError() on active object.
        r = activeObject->RunError();

        // If RunError() didn’t handle the error,  
        // call CActiveScheduler::Error(). 
        if (KErrNone!=r)
           Error(r);
        }
     // Event handled, break out of lookup loop and recheck
     // the thread's request semaphore.
     break;
     }
  }
}

Cancelling a request

An active object must be able to cancel an outstanding asynchronous request. The destructor of the active object should call the base class Cancel() method. If an asynchronous request is outstanding Cancel() calls the active object's DoCancel() and waits for notification that the request has terminated.

Copyright note

The figure in this topic is reproduced with permission from a Symbian Foundation wiki article Active Objects which is part of the series The Fundamentals of Symbian C++. The version used was that available at http://developer.symbian.org/ on 3 November 2010. The content in this page is licensed under the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License (http://creativecommons.org/licenses/by-sa/2.0/uk).

Related concepts