An active object encapsulates an asynchronous request and the behaviour that is required when the request completes.
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.
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()
.
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.