Wiley.Symbian.OS.C.plus.plus.for.Mobile.Phones.Aug.2007 (779890), страница 32
Текст из файла (страница 32)
The act of calling a RunL() is something that we will refer toas a RunL() dispatch.MULTITASKING AND PRE-EMPTION157Some events are more important than others. It is much better tohandle events in priority order than in a simple first-in, first-out (FIFO)order. Events that control the thread (keypress events to an application, forexample) can be handled with higher priority than others (for example,some types of animation). But, once a RunL() has started – even fora low-priority event – it runs to completion. No other RunL() can becalled until the current one has finished. That is not a problem providedall your event handlers are short and efficient.Non-pre-emptive multitasking is surprisingly powerful.
Actually, thereshould be no surprise about this; it’s the natural paradigm to use forevent handling. For example, the window server handles key and pointerevents, screen drawing, requests from every GUI-based application inthe system, and animations including a flashing text cursor, sprites andself-updating clocks. It delivers all this sophistication using a single threadwith multitasking based on active objects.In many systems, the preferred way to multitask is to multithread.
InSymbian OS, the preferred way to multitask is to use active objects.In a truly event-handling context, active objects give only gains overusing threads: you lose no functionality, as events occur sequentially andcan be handled in priority order. You gain convenience over threadsbecause you know an active object cannot be pre-empted; you donot need to use mutexes, semaphores, critical sections, or any kind ofsynchronization to protect against the activities of other active objects inyour thread. Your RunL() is guaranteed to be an atomic operation.
It isalso more efficient; you do not incur the overhead of a context switch,nor the per-thread memory overhead of both user-side and kernel-sidestacks, when switching between active objects.Non-pre-emptive multitasking in Symbian OS is not the same as cooperative multitasking. In co-operative multitasking, one task says: ‘I amnow prepared for another task to run’ for instance by using Yield() orsome similar function. What this really means is, ‘I am a long-runningtask, but I now wish to yield control to the system so that it can get anyoutstanding events handled if it needs to’. Active objects do not work likethat – during RunL(), your event-handling code is the only code thatexecutes within your thread.
Only after RunL() has finished can otherevents be handled by your application.All multitasking systems require a degree of co-operation so thattasks can communicate with each other where necessary. Active objectsrequire less co-operation than threads because they are not pre-emptivelyscheduled. They can be just as independent as threads. As mentionedearlier, each thread can contain an active scheduler.
The thread’s activescheduler manages active objects independently of one another, just as158ACTIVE OBJECTSthe kernel scheduler manages threads independently of one another. Itis worth mentioning that, whilst active objects themselves are non-preemptive, the threads in which they reside are scheduled pre-emptively.6.3 A More In-depth Look at Active ObjectsAn event-handling thread has a single active scheduler, which is responsible for deciding the order in which events are handled. It also has one ormore active objects, derived from the CActive class, which are responsible both for issuing requests (which later result in an event happening)and for handling the event when the request completes. When an eventoccurs, the active scheduler calls RunL() on the active object associatedwith the completed event.The active scheduler is, in effect, a ‘wait loop’ or wait-object that waitsfor events.
On receipt of a signal marking the occurrence of an event, theactive scheduler decides which event, of the many it may be expecting,has occurred and then dispatches the RunL() function of the appropriateactive object.Symbian OS makes extensive use of active objects, particularly at theUI layer. For example, the UI framework always maintains an outstandingrequest to the window server for user input and other system events. Whenan event occurs and this request is completed, it calls the appropriateapplication UI and control functions to ensure that the application handlesthe event properly.
So, ultimately, all application code is handled underthe control of an active object’s RunL().Because this framework is provided for you, you can get started withSymbian OS application programming without knowing exactly howactive objects work. However, for more advanced GUI programming,and for anything to do with servers or any service that is requestedas an asynchronous request, you do need to know how they work indetail. With this understanding, you can not only use the large array ofasynchronous services provided by the operating system, but you canalso create your own.The best way to understand how to use active objects is to look ata simple example.
The example shown in Figure 6.3 demonstrates theuse of two active objects. Figure 6.3 shows what it looks like when youlaunch the program and press the ‘Options’ key to display its menu.The Set hello menu item triggers a Symbian OS timer. Three secondslater, that timer completes, causing an event. This event is handled byan active object, which puts a message saying "Hello world!" on thescreen.
During that three-second period, you can select the Cancel menuitem to cancel the timer, so that the message never appears.The Start flashing menu item starts to flash the message and Stopflashing stops it. The flashing is implemented by an active object thatA MORE IN-DEPTH LOOK AT ACTIVE OBJECTS159Figure 6.3 Two active objects on a menucreates regular timer events and then handles them. Its event handlingchanges the visibility of the text and redraws the view.The two active objects represent two different styles of usage:• a one-off event is generated and its completion handled• an event is generated, and as part of the handling of the completionof that event, a further asynchronous request is issued and thereforeanother event is generated so that we have a continuous cycle, brokenonly by selecting the Stop Flashing menu item.The Set Hello Menu ItemThe behavior that we see when the Set hello menu item is selected ishandled by the CDelayedHello class:class CDelayedHello : public CActive{public:// Construct/destructstatic CDelayedHello* NewL();∼CDelayedHello();// Requestvoid SetHello(TTimeIntervalMicroSeconds32 aDelay);private:// Construct/destructCDelayedHello();void ConstructL();// from CActivevoid RunL();void DoCancel();TInt RunError(TInt aError);private:RTimer iTimer;// HasCEikonEnv* iEnv;// Uses};160ACTIVE OBJECTSFrom the declaration, you can see that CDelayedHello:• ‘is-a’ active object, derived from CActive• ‘has-a’ event generator, an RTimer object• defines a request function, SetHello(), that requests an event fromthe RTimer• implements the RunL() function to handle the event generated whenthe timer request completes• implements the DoCancel() function to cancel an outstanding timerrequest• overrides the default implementation of RunError() to handle thecase if RunL() leaves.CActiveCDelayedHelloRTimerRunL()=0DoCancel()=0RunError()Cancel()SetActive()RunL()DoCancel()RunError()SetHello()After()iStatusiTimerFigure 6.4 The basic pattern of an active objectAll active object classes share this basic pattern (see Figure 6.4).
Theyare derived from CActive and implement its RunL(), DoCancel()and RunError() functions. They include an event generator and at leastone request function.ConstructionConstruction of a CDelayedHello active object is implemented by theNewL() static function, which follows the standard pattern that we sawwhen we looked at error handling and cleanup. Here it is:CDelayedHello* CDelayedHello::NewL(){CDelayedHello* self = new (ELeave) CDelayedHello();CleanupStack::PushL(self);self->ConstructL();CleanupStack::Pop(self);return self;}CDelayedHello::CDelayedHello() : CActive(CActive::EPriorityStandard){CActiveScheduler::Add(this);}A MORE IN-DEPTH LOOK AT ACTIVE OBJECTS161The C++ constructor is required for any derived active object class.Inside it, you call the CActive constructor to specify the active object’spriority.
The active scheduler uses this value to decide which activeobject is to be dispatched first if it finds that more than one event is readyto be handled. You should specify CActive::EPriorityStandardhere unless there are good reasons to specify something lower or higher(we look at those reasons later). The new object adds itself to theactive scheduler, so that the active scheduler can include it in eventhandling. Adding the object to the active scheduler does not allocatememory; which is why we can do this in the C++ constructor – it cannotleave.void CDelayedHello::ConstructL(){iEnv = CEikonEnv::Static();User::LeaveIfError(iTimer.CreateLocal());}The second-phase constructor, the ConstructL() function, gets apointer to the UI environment, and then uses iTimer.CreateLocal()to request that the kernel create a kernel-side timer object, which weaccess through the RTimer handle.
If there is any problem here, it leaves.DestructionCDelayedHello::∼CDelayedHello(){Cancel();iTimer.Close();}Before we destroy the active object, the destructor must cancel anyoutstanding requests for timer events. We do this by calling the standardCActive function Cancel(). It works by checking to see whethera request for a timer event is outstanding and, if so, calls DoCancel() to handle it.
Cancel() should be called before other resourcesare destroyed in case they are used by the service provider or theDoCancel() method.An active object must implement a DoCancel() function and mustalso call Cancel() in its destructor.The destructor closes the RTimer object, which destroys the corresponding kernel-side object. After this, the base CActive destructor isinvoked and this removes the active object from the active scheduler.162ACTIVE OBJECTSRequesting and handling eventsSetHello() requests a timer event after a given delay:void CDelayedHello::SetHello(TTimeIntervalMicroSeconds32 aDelay){_LIT(KDelayedHelloPanic, "CDelayedHello");__ASSERT_ALWAYS(!IsActive(), User::Panic(KDelayedHelloPanic, 1));iTimer.After(iStatus, aDelay);SetActive();}Every line in this function is important.• First, we assert that no request is already outstanding (thatIsActive() is false).