Using Asynchronous Services with Active Objects

Symbian OS is a heavily asynchronous operating system. Virtually all system services are provided through servers, which operate in their own process for high reliability. (A process is a unit of memory protection, and it isolates the application thread from all other processes.) Service provider APIs typically have asynchronous and synchronous versions of their functions available to their client applications, but to avoid blocking the application's user interface you would usually use the asynchronous version. Most time-consuming operations are therefore made as a request to some asynchronous service provider, such as the file system (this will be introduced in the next section). The service request function returns immediately, while the request itself is processed in the background. This means that the program needs to receive some notification in the future when the request is complete, and in the meantime it needs to be able to get on with other tasks like responding to user input or updating the display.

Such asynchronous systems are prevalent in modern computing, and there are a number of ways of implementing them:

One popular practice is to use multiple threads in a preemptive system: a new execution thread is spawned to handle each asynchronous task, making the request and awaiting completionoften by polling the request provider to see if the request has completed yet. Although this approach is possible in Symbian OS, it is inefficient and strongly discouraged.

Further information on implementing multithreading can be found under the RThread API reference section of the SDK documentation, but this approach should rarely be used, and further explanation is beyond the scope of this book.


Another alternative, and the one favoured for Symbian OS applications, is to use cooperative multitasking . In a preemptive system, the operating system's kernel decides the current thread has had enough use of the processor, and allows another thread to preempt it, usually before it has completed processing. Cooperative (or non-preemptive) multitasking requires that the currently running task gives up use of the processor and allows other tasks to run. All application processing occurs within a single thread, and so the multiple tasks must cooperate.

Cooperative multitasking is usually implemented as some kind of wait or message loop, and this is the case with Symbian OS, as shown in Figure 3-6.

Figure 3-6. A cooperative multitasking wait loop.


The loop starts by waitingthe single application thread is blocked until a service request completes, and the service provider ("server"), which is running in a separate thread (or process), signals that it has completed by incrementing a semaphore. Symbian OS does not poll, as this wastes processor cycles and therefore battery power. Note also that it is important that at least one request is made before the start of the loop, or the wait loop will never be signaled!

Once the wait loop has been signaled, execution can continue. The system searches through each of the outstanding task requests until it finds a completed task and runs its handler function. The loop then waits for the completion of the next request.

If multiple requests complete at around the same time, then the first completed task found may not be the task that signaled. However, as all completed tasks will have incremented the semaphore, this will likely be handled on the next iteration of the loopevent completion signals are queued and handled in sequence, but only one request completion is handled in each iteration of the loop.

To easily allow a Symbian OS application, which typically consists of a single thread within its own process, to issue multiple asynchronous requests and be notified when any of the current requests have completed, a supporting framework has been provided. Cooperative multitasking is achieved through the implementation of two types of object: an Active Scheduler for the wait loop (one per threadthe static functions provided always refer to the current one), and an Active Object for each taskthis encapsulates the request and the corresponding handler function (called upon request completion).

Note that all processing in a GUI application takes place in an Active Object, but much of this, along with the creation of the Active Scheduler, is hidden by the Application Framework. GUI applications will be covered in Chapter 4.

In a console application or DLL, you must create and install your own Active Scheduler.

The Active Scheduler

The Active Scheduler is implemented by CActiveScheduler , or a class derived from it. It implements the wait loop within a single thread, detecting the completion of asynchronous events and locating the relevant Active Object to handle them.

The Active Scheduler maintains a list, ordered by priority, of all Active Objects in the system. The ordering of Active Objects of equal priority is unspecified.

Completed events are detected through a call to User::WaitForAnyRequest() . This call suspends the thread until an asynchronous request has completed. When the thread is signaled, the Active Scheduler cycles through its list of Active Objects in priority order to find an Active Object that has completed. Each Active Object is checked to see if it has issued a request (its iActive flag is set), and if the request has completed ( iStatus is set to a value other than KRequestPending note that the request may have returned an error). If both of these conditions are met, the Active Object is first made inactive (its iActive flag is unset), so that it will not be found again next time there is a completed request (unless its request has actually been reissued)remember that the Active Scheduler maintains a list of all Active Objects, whether they have currently requested a service or not. Then its handler function is called.

The handler function for an active object is called RunL() . Note that since this function may leave, the Active Scheduler calls it from within a trap harness. If a leave occurs, an attempt is made to invoke the Active Object's own exception handler, RunError() . If that returns an error, the Active Scheduler's error-handling routine is called, which by default will panic the thread.

Once the completed Active Object is found, the loop waits again. If the Active Scheduler reaches the end of its list without finding a completed Active Object, then this indicates that the Active Object that issued the request was not in its list and results in a stray-event panic.

Active Objects

Active Objects represent asynchronous service requests, encapsulating:

  • A data member representing the status of the request ( iStatus ).

  • A handle on the asynchronous service provider (usually an R -class object).

  • Connection to the service provider during construction.

  • The function to issue (or reissue ) the asynchronous request (user-defined).

  • The handler function to be invoked by the Active Scheduler when the request completes ( RunL() ).

  • The function to cancel an outstanding request ( Cancel() ).

Here is part of the definition of the abstract base class of all Active Objects, CActive :

 class CActive : public CBase    { public:    ~CActive();    void Cancel();    inline TBool IsActive() const; protected:    CActive(TInt aPriority);    void SetActive();    virtual void DoCancel() = 0;    virtual void RunL() = 0;    virtual TInt RunError(TInt aError); public:    TRequestStatus iStatus;    }; 

First, note the public members : a Cancel() function that is called when you want to cancel the request, and a function that checks whether the Active Object is active or not ( IsActive() ). The public destructor of your derived Active Object should always call Cancel() to make sure that any outstanding requests are cancelled before the Active Object is destroyed if there are no outstanding requests, then Cancel() does nothing, so there is no need to check this explicitly. Note also that the iStatus member is publicthis will be covered in more detail later in this section.

It is also worth pointing out that there is no actual function in the base class to issue an asynchronous requestit is up to you to define one in your derived class. This is often called Start() .

The protected constructor prevents stack instantiation and takes a priority parameter. This is used to order the Active Objects in the Active Scheduler's list. The higher the priority, the closer to the top of the list the Active Object will be, and the more likely it is to be handled before the others in the event of multiple requests being completed at once. Normally an Active Object is given a standard priority of EPriorityStandard available priorities will be covered later in this section.

It must be stressed that, regardless of priority, Active Objects cannot preempt each other.


The final four methods are the crucial ones:

SetActive() must be called once a request has been issued, otherwise the Active Scheduler will ignore it when searching for a completed Active Object, causing an error.

DoCancel() is a pure virtual function that must be implemented to provide the functionality necessary to cancel the outstanding request, but note that it should never be invoked directly always use Cancel() , which will invoke DoCancel() for you and also ensure that the necessary flags are set to indicate the request has completed.

RunL() is the asynchronous event-handler function. This is the function called by the Active Scheduler when the outstanding request has completed. What it does will obviously depend entirely upon the nature of the service requestedwhatever processing is necessary once the request has completed. You will often see an active object used to implement a state machinein this case, each time the RunL() is called, the next step of a complex series of interdependent asynchronous events is launched.

RunError() is called (by the Active Scheduler) if RunL() leaves it gives the Active Object a chance to handle its own errors. If able to handle the error, RunError() should return KErrNone . Otherwise it should simply return the error it was passed as the argument. In this case, the error will then be passed to the Active Scheduler's Error() function, with a default behavior of causing a panic.

Remember that a set of Active Objects belong to a single thread and are cooperating togetherthe RunL() of one Active Object cannot preempt the RunL() of another. What this really means is that once the Active Scheduler has invoked one RunL() , no others (within the same thread) can run until this one has completed and returned. This means that all RunL() functions must be kept as short as possible, and this is most crucial in any GUI application, since all of the code keeping the UI responsive is also running in other Active Objects within the same thread. If your RunL() takes ten seconds to complete, your application will appear to freeze for this period of timeno keypresses will be processed, animations will stop and so on. As a general rule of thumb, no RunL() should take any longer than one-tenth of a second to completeotherwise the responsiveness of your application will suffer.

Notice finally the TRequestStatus iStatus member. It is basically just an encapsulated integer that is used to represent the status or error code returned by the asynchronous service provider. A reference to the Active Object's iStatus member is passed to the service provider (via the Kernel) when the request is made. The service provider's first task is to set it to KRequestPending . When the requested service completes, the service provider will set the value of iStatus to either KErrNone (if the request completes successfully) or an error code.

Implementing an Active Object

The rest of this section covers the practicalities of writing an active object. You are first provided with a checklist summary of the tasks involved and then guided through a full example of how to apply this information to create a simple Active Object-based timer. Finally, common pitfalls that occur when using Active Objects are covered.

Active Object Checklist

Writing an Active Object is actually quite simple. It is summarized in the following steps:

1.
Create a class derived from CActive .

2.
Encapsulate the appropriate asynchronous service provider handle or handles (usually R -classes) as member data of the class.

3.
Invoke the constructor of CActive , specifying the appropriate task priorityusually the default priority.

4.
Connect to the service provider in your ConstructL() method.

5.
Invoke CActiveScheduler::Add() in ConstructL() (unless there is good reason not toit must be called somewhere).

6.
Implement NewL() and NewLC() as normal.

7.
Implement your asynchronous request function, often called Start() or StartL() . This should invoke the appropriate asynchronous service function on the service provider handle ( R -class), specifying iStatus as the TRequestStatus& argument. Do not forget to call SetActive() !

8.
Implement your RunL() method to handle any necessary work once the request is complete. This may involve processing data, notifying an observer of completion or reissuing the request.

9.
Implement DoCancel() to handle canceling of the request. This will often simply be a case of invoking the appropriate cancel function on the service provider handle.

10.
Optionally override RunError() to handle any leaves from RunL() . The default implementation will cause the Active Scheduler to panic!

11.
Implement the destructor to call Cancel() and close the handle(s) on the service provider(s).

Using the Active Object is then simply a case of applying the following steps:

1.
Instantiating via NewL() or NewLC() as appropriate.

2.
Calling Start() , or whatever function you implemented to make the initial request.

3.
If you wish to cancel the request prior to completion, simply call Cancel() .

A Practical Example

The Elements example application shows the use of Active Objects. It loads lists of element data from three different CSV (Comma-Separated Value) files using an asynchronous interface to the File Server. Active Objects control the reading of data, which is read in a line at a time to break up a potentially long-running task into smaller chunks . This also allows all files to be effectively loaded " simultaneously ."

This part of the example has been contrived to demonstrate asynchronous processing, but as Symbian OS is so efficient, a second type of asynchronous request, a timer, has to be introduced to slow the system down in order to illustrate the asynchronous loading process! The file loader class works as a simple state machine: it either loads data or wastes time on a small pseudo-random delay.

Note that Symbian OS already provides a simple timer class, CTimer , but it is instructive to look at how this might otherwise be implemented. File operations will be explained properly in the next section.

The file loader class is called CCsvFileLoader . Here is part of its class definition; note that it derives from CActive :

 class CCsvFileLoader : public CActive    { public:    void Start(); private:    CCsvFileLoader(RFs& aFs, CElementList& aElementList,       MCsvFileLoaderObserver& aObserver);       void ConstructL(const TDesC& aFileName);    // From CActive    void RunL();    TInt RunError(TInt aError);    void DoCancel(); private:    TFileName iFileName;    RFile iFile;    TBool iWastingTime;    RTimer iTimeWaster;    }; 

Construction

Two-phase construction is almost always needed for Active Objects, since they usually need to connect to their asynchronous service provider, which may fail. Here is the implementation of the first-phase constructor for the file loader:

[View full width]
 
[View full width]
CCsvFileLoader::CCsvFileLoader(RFs& aFs, CElementList& aElementList, MCsvFileLoaderObserver& aObserver) : CActive(EPriorityStandard), ... { }

Note that CCsvFileLoader() calls the constructor of CActive with a hard-coded priority, in this case EPriorityStandard . The standard priorities are defined in e32base.h as:

 enum TPriority    {    EPriorityIdle = -100,    EPriorityLow = -20,    EPriorityStandard = 0,    EPriorityUserInput = 10,    EPriorityHigh = 20,    }; 

Choose whichever is most appropriate for your Active Object, remembering that all it affects is the order in the Active Scheduler's list. It is rare that an Active Object in application code is given a priority of anything other than EPriorityStandard if you have to worry about specific priorities, then there may be something wrong with your design! For reference, the priorities given to system tasks for UI applications are defined in the class TActivePriority in coemain.h .

Here is the implementation of the second-phase constructor for the file loader:

 void CCsvFileLoader::ConstructL(const TDesC& aFileName)    {    iFileName = aFileName;    User::LeaveIfError(iFile.Open(iFs, iFileName, EFileRead));    User::LeaveIfError(iTimeWaster.CreateLocal()); // Not done automatically!    CActiveScheduler::Add(this);    } 

It sets the filename and connects to the two asynchronous service providers required via their handles: RTimer and RFile . If there is insufficient memory for the timer, or the file does not exist, then ConstructL() (and so NewL() or NewLC() ) will leave.

Finally, this Active Object is added to the Active Scheduler's list. This does not happen automatically, so it is commonly performed as the last line in the second-phase constructor, after construction has been successful.

Each Active Object must be added to the Active Scheduler once and once only. Failing to add it will result in a stray-request panic.


Note that construction is normally wrapped up in NewL() or NewLC() methodsthese have been omitted here.

Starting the Active Object

Here is the implementation of Start() :

 void CCsvFileLoader::Start()    {    // Wait for a randomish amount of time before doing anything else    TInt delay = (iFileName.Size() % 10) * 100000;    iTimeWaster.After(iStatus, delay);    SetActive();    } 

This is the function that will be called to start the initial asynchronous requestin this case, all it does is wait for a period of time before the RunL() method is called. Remember, though, that the Start() function itself will return almost immediately RunL() will be called at some point in the future, once the asynchronous timer request completes.

A "random" delay time is calculated, based on the length of a filename (not very random, but random enough!). The class member iTimeWaster is an RTimer instancethe handle on an asynchronous service provider. The After() method takes two argumentsthe time delay in microseconds (millionths of a second) and a trequestStatus .

Note that the presence of a trequestStatus denotes an asynchronous function. All asynchronous functions in Symbian OS take a trequestStatus& , which they use to notify the owning Active Object of completion.

After the given period of time, the asynchronous service provider (running in another thread/process) will signal the thread by doing two things:

  • Incrementing the thread's semaphore.

  • Setting the given TRequestStatus to something other than KRequestPending (hopefully KErrNone if all is well).

Incrementing the semaphore reawakens the thread, if suspended . (Remember the WaitForAnyRequest() at the top of the wait loop suspends the current thread.) Changing the value of the TRequestStatus ( iStatus ) will allow the Active Scheduler to tell which Active Object has completed, and this will result in its RunL() method being called.

Failing to call SetActive() once a request has been issued will result in a stray-request panic when the request completes.


RunL()

The RunL() of this particular Active Object is fairly complex, because it performs a number of tasks:

  • It decides what the next iteration should do (load data or waste time).

  • It checks the status of the last iteration and reports this to the observer.

  • It processes any data loaded.

The RunL() of any general Active Object will probably want to do at least one of the jobs suggested abovesome may want to do all three.

Here are the first few lines of RunL() :

 void CCsvFileLoader::RunL()    {    if (!iHaveTriedToLoad)       {       // Fill the read buffer if we haven't yet tried to do so...       FillReadBufferL();       return;       } 

In other words: if this is the first time RunL() has been called, then you need to load some data first. It will become obvious later why this is important.

FillReadBufferL() looks like this:

 void CCsvFileLoader::FillReadBufferL()    {    iHaveTriedToLoad = ETrue;    // Seek to current file position    User::LeaveIfError(iFile.Seek(ESeekStart, iFilePos));    // Read from the file into the buffer    iFile.Read(iReadBuffer, iReadBuffer.MaxSize(), iStatus);    SetActive();    } 

Firstly, you need to set iHaveTriedToLoad to true, since you have now tried to load something.

Next, seek to the right position in the file. The iFile member is an RFile object whose API will be discussed in more depth in the next section.

The crucial steps come next. RFile::Read() is another asynchronous function. Again, you can tell this by the presence of the trequestStatus object, iStatus . And as before, this call will cause RunL() to be called once the operation is complete. SetActive() is invoked to ensure this.

Here is the next section of the RunL() , which handles error notification:

 if ((iStatus.Int() != KErrNone)  (iReadBuffer.Size() == 0))    {    iObserver.NotifyLoadCompleted(iStatus.Int(), *this);    return;    } 

Notice the first test in the if statement. trequestStatus::Int() just returns the error value of the trequestStatus as an integer. This error value is returned by the asynchronous service provider as a way of indicating to the Active Object whether the operation was successful or not.

Generally speaking, you should always check the error value of the iStatus member to see if the asynchronous function was a success. It is important to stress that RunL() will always be called, whether or not the asynchronous call succeeded.


In the example above, you notify the observing client if the asynchronous call failed, or if no data was read from the file. If the latter is true, it denotes that the end of file was reached. In either case, the completion status is returned to the user, and you return without reissuing the request . That is basically the end of the Active Object's useful life (unless Start() is called again).

Assuming that there were no errors and some data has been read in, the next step is to decide whether this iteration is to simply waste time. If so, you reissue the time-delay request much like the first time:

 if (iWastingTime)    {    // Just wait a randomish amount of time    TInt delay = (iFilePos % 10) * 100000;    iTimeWaster.After(iStatus, delay);    SetActive();    // Don't waste time next time around!    iWastingTime = EFalse;    return;    } 

You can see here that the file position is used to supply the random seed, but other than that it is basically the same as the code in Start() . In addition, iWastingTime is set to false, preventing the time-wasting step from being executed on the next iteration.

The final section of the RunL() processes the data in the buffer and reissues the load request by calling FillReadBufferL() again. It also sets iWastingTime TRue, ensuring a time-wasting step on the next iteration to slow things down a bit:

 // Extract and convert the buffer TPtrC8 elementBuf8 = ExtractToNewLineL(iReadBuffer); HBufC* elementBuf16 = HBufC::NewLC(elementBuf8.Length()); TPtr elementPtr16 = elementBuf16->Des(); elementPtr16.Copy(elementBuf8); // Read the element from the buffer iElementList.AppendL(*elementBuf16); // Report the element that's just been loaded to the observer TInt lastElementLoadedIndex = iElementList.NumberOfElements() - 1; iObserver.NotifyElementLoaded(lastElementLoadedIndex); // Increment the file position iFilePos += elementBuf16->Length() + KTxtLineDelimiter().Length(); // Reissue the request FillReadBufferL(); // Cleanup CleanupStack::PopAndDestroy(elementBuf16); iWastingTime = ETrue; 

Handling Errors in RunError()

It is perfectly permissible for RunL() to leavethe trailing " L " denotes this. If it does, then the error code that it leaves with is passed to RunError() :

 TInt CCsvFileLoader::RunError(TInt aError)    {    // Notify the observer of the error    iObserver.NotifyLoadCompleted(aError, *this);    return KErrNone;    } 

In this instance, that is all there is to it. You simply notify the observer that the load has completed (albeit unsuccessfully, with the appropriate error code), but return KErrNone to show that the error has been handled locally. Any nonzero error would be passed to the Active Scheduler's Error() function.

Other error handlers may do more complex things such as retrying or the like, but commonly they will do something similar to the example above and just pass on notification of the error.

Canceling an Outstanding Request

All Active Objects must implement a DoCancel() method to cancel any outstanding request. Here is the implementation for CCsvFileLoader :

 void CCsvFileLoader::DoCancel()    {    if (iWastingTime  !iHaveTriedToLoad)       {       iTimeWaster.Cancel();       }    } 

All you need to do is invoke RTimer::Cancel() if the timer is running. Since there is no way of canceling an outstanding RFile request, there is nothing you need to do in that case.

DoCancel() is called by CActive::Cancel() . CActive::Cancel() itself should never be overridden (it is non-virtual anyway), as it does a number of important things for you:

  • It checks to see if the Active Object is actually activeif not, it just returns without doing anything.

  • It invokes DoCancel() .

  • It waits for the request to completethis must complete as soon as possible. (Note that the original request may actually complete before it is cancelled.)

  • It sets iActive to false.

Understanding that Cancel() waits for completion of the outstanding request is crucial. This means that RunL() will not get called as a result of calling Cancel() as you might expectso any cleanup needed as a result of a Cancel() must be handled in DoCancel() , not in RunL() .

You should never call the DoCancel() function of an Active Object explicitly. If you want to cancel a request, then just call Cancel() this will invoke DoCancel() and also ensure that RunL() is not called.


Destructor

The destructor implementation is shown here:

 CCsvFileLoader::~CCsvFileLoader()    {    Cancel();    iFile.Close();    iTimeWaster.Close();    } 

The first thing that should happen in any Active Object destructor is a call to Cancel() any outstanding requests. If an Active Object is deleted with a request outstanding, a stray-request ( E32USER-CBase 40 ) panic will result.

Any handles to the asynchronous service providers must be closed to avoid resource leakage.

The base CActive destructor will automatically call Deque() to remove the Active Object from the Active Scheduler's list.

Starting the Active Scheduler

The UI framework will automatically create, install and start an Active Scheduler for you, so this can be skipped if you are not going to be writing .exe s (console applications or Symbian OS Servers), or DLLsthese need an Active Scheduler to be started explicitly. However, it does provide some additional insight into how Active Objects and the Active Scheduler interact.

Before an Active Scheduler can be started, a number of steps must be undertaken:

  • The Active Scheduler must be instantiated .

  • It must be installed into the thread.

  • An Active Object must be created and added to the Active Scheduler.

  • A request must be raised (as the wait loop suspends the thread).

Only then can the scheduler be started.

All of these steps can be seen in the following code example:

 void DoExampleL()    {    CActiveScheduler* scheduler = new (ELeave) CActiveScheduler;    CleanupStack::PushL(scheduler);    CActiveScheduler::Install(scheduler);    CElementsEngine* elementEngine = CElementsEngine::NewLC(*console);    elementEngine->LoadFromCsvFilesL();  // Issues requests    CActiveScheduler::Start();    CleanupStack::PopAndDestroy(2, scheduler);    } 

The first three lines are fairly obviousthey instantiate a new CActiveScheduler object, add it to the Cleanup Stack, and then install it as the current Active Scheduler for the threadonly one Active Scheduler can be installed in each thread.

The next two lines create the CElementsEngine , which owns Active Objects (three CCsvFileLoader objects) and starts them off.

Since there are now some outstanding requests, the Active Scheduler can be started. The call to the CActiveScheduler::Start() function does not return until a corresponding call is made to CActiveScheduler::Stop() from within the RunL() of one of the Active Objects. When it does, the Active Scheduler and engine are deleted, and the thread exits.

There are a number of issues here of profound importancethe CActiveScheduler::Start() function pretty much covers the entire lifespan of the thread. To be able to discuss these issues further, consider the lifecycle of the Active Scheduler. This is illustrated in Figure 3-7.

Figure 3-7. Lifecycle of the Active Scheduler.


The four "objects" involved (Executable, Active Scheduler, Active Object and Service Provider) are shown as vertical columns . Time runs from top to bottom.

When the executable's main thread is launched, it instantiates and installs the Active Scheduler, as shown previously. Then, at least one Active Object must be created and added to the Active Scheduler. Once done, the Active Object must issue a request. The asynchronous service provider (in another thread and usually another process) starts by setting the Active Object's iStatus member to KRequestPending . The service provider can then set about servicing the request.

Once there is a request outstanding, it is safe to Start() the Active Scheduler. This causes the Active Scheduler to enter its wait loop, starting with a call to WaitForAnyRequest() , decrementing the thread's semaphore, and suspending the thread.

This is why it is essential to have an outstanding request before starting the Active Scheduler. Without an outstanding request, there is no way for the thread to be reawakenednothing will increment the thread's semaphore, and the thread will remain suspended permanently.

At some point the service provider will complete the request and signal this by incrementing the requesting thread's semaphore and setting the Active Object's iStatus member to the appropriate error code. The scheduler then searches through its list to find the Active Object that has just completed (is active and has an iStatus not equal to KRequestPending ) and runs its RunL() .

What happens next depends upon the RunL() . It will handle the completion of the request and may reissue the request or spawn another Active Objectin these cases the cycle repeats (although the Active Scheduler will not be started again).

Alternatively, the Active Object's RunL() may call CActiveScheduler::Stop() . This will cause the Active Scheduler to exit its wait loop, returning control from the call to CActiveScheduler::Start() . The executable will usually then clean up and exit. Remember that if you are creating a UI application, the Active Scheduler will be created and controlled by the framework and you will not need to call CActiveScheduler::Stop() for the main Active Scheduler loop explicitlyin fact you should not do this, just let the application framework handle it.

Calling CActiveScheduler::Start() a second time creates a nested wait loopthis is used for modal processing. For example, modal dialogs need to "pause" the main processing of the application thread but must still allow processing of input and so on. A call to CActiveScheduler::Stop() will return control to the initial wait loop. This is an advanced topic, and further details are beyond the scope of this book.


As you can see, the lifespan of the Active Scheduler is pretty much the same as that of the owning executable thread. Certainly this is true of a UI framework application, where the Active Scheduler is created by the framework and populated with a few Active Objects that are responsible for maintaining the UI, responding to user input, and so on.

Again, it is important to reflect that almost all code written by a developer in a UI application is running within the RunL() of an Active Object, so it needs to be made as efficient as possible to keep the system responsive. Long-running synchronous tasks will prevent other Active Objects from running, reducing the application's responsiveness.

Common Active Object Pitfalls

The most common problem with an Active Object is a stray-event panic from the Active Scheduler. This is usually caused by one (or more) of the following:

  • Forgetting to call CActiveScheduler::Add() before starting the Active Object.

  • Not calling SetActive() after issuing (or reissuing) an asynchronous request.

  • Passing the same iStatus to two service providers at the same time (thereby having multiple requests outstanding on the same Active Object).

Do not invoke DoCancel() directly. It should be private anywayalways call Cancel() , and never call Cancel() from within DoCancel() ! Note that Cancel() should always be called in the destructor of your derived class. If there is an outstanding request when the base CActive destructor is called, an E32USER-CBase 40 panic is raised.

Never forget that Active Objects use cooperative multitaskingremember that no Active Object can preempt another, nor should one RunL() take any longer than about one-tenth of a second to complete. Very long-running RunL() functions may cause a View Server Time Out panic ( ViewSrv 11 )this occurs when an application fails to respond to the system within a given time (around ten seconds).

If your Active Object repeatedly reissues requests, then a View Server Time Out panic may also occur if there is insufficient time between iterationsyour Active Object may be "hogging" the Active Scheduler and not allowing lower-priority Active Objects to complete. If you want to have one Active Object called repeatedly, use a technique similar to that outlined in the example, using a timer to give a reasonable delay (at least a few hundredths of a second or so). This delay does not necessarily have to be after every requestin a game application, for example, you may just want to drop an occasional frame to allow other processing to take place.

Note that if you are deriving from, or using CTimer to create a delay, you need to Add() it to the Active Schedulerthis is not performed by default in the base class! Further details of CTimer can be found in the SDK documentation.

Don't forget that if you are writing a console application or DLL, an Active Scheduler is not provided by default. Without a valid Active Scheduler installed, any use of Active Objects will result in an E32USER-CBase 44 panic.



Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net