Symbian OS ExplainedEffective C++ Programming for Smartphones phần 5 docx

39 204 0
Symbian OS ExplainedEffective C++ Programming for Smartphones phần 5 docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

ACTIVE OBJECT BASICS 129 object should handle the completed request. It uses its priority-ordered list of active objects, inspecting each one in turn to determine whether it has a request outstanding. It does so by checking the iActive flag; if the object does indeed have an outstanding request, it then inspects its TRequestStatus member variable to see if it is set to a value other than KRequestPending. If so, this indicates that the active object is associated with a request that has completed and that its event handler code should be called. Having found a suitable active object, the active scheduler clears the active object’s iActive flag and calls its RunL() event handler. This method handles the event and may, for example, resubmit a request or generate an event on another object in the system. While this method is running, other events may be generated but RunL() is not pre-empted – it runs to completion before the active scheduler resumes control and determines whether any other requests have completed. Once the RunL() call has finished, the active scheduler re-enters the event processing wait loop by issuing another User::WaitForAny- Request() call. This checks the request semaphore and either suspends the thread (if no other requests have completed in the meantime) or returns immediately (if the semaphore indicates that other events were generated while the previous event handler was running) so the scheduler can repeat active object lookup and event handling. Here’s some pseudo-code which represents the basic actions of the active scheduler’s event processing loop. 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 & 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 (KErrNone!=r) // RunError() didn’t handle the error, Error(r); // call CActiveScheduler::Error() } break; // Event handled. Break out of lookup loop & resume 130 ACTIVE OBJECTS UNDER THE HOOD } }// End of FOREVER loop } If a single request has completed on the thread in the interim, the active scheduler performs lookup and calls the appropriate event handler on that active object. If more than one request has completed in that time, the active scheduler calls the event handler for the highest priority active object. It follows that, if multiple events are generated in close succession while another event is being handled, those events may not be handled in the sequence in which they occurred because the active object search list is ordered by priority to support responsive event-handling. Normally, active object code should be designed so the priority does not matter, otherwise the system can become rather delicate and be thrown off balance by minor changes or additional active objects on the thread. However, to be responsive, say for user input, it is sometimes necessary to use a higher priority value. Long-running, incremental tasks, on the other hand, should have a lower priority than standard since they are designed to use idle processor time (as I’ll describe later in this chapter). It’s important to understand that the priority value is only an indication of the order in which the active scheduler performs lookup and event- handling when multiple events have completed. In contrast to the priority values of threads used by the kernel scheduler, it does not represent an ability to pre-empt other active objects. Thus, if you assign a particular active object a very high priority, and it completes while a lower-priority active object is handling an event, no pre-emption occurs. The RunL() of the lower-priority object runs to completion, regardless of the fact that it is ”holding up” the handler for the higher-priority object. On Symbian OS, you cannot use active object priorities to achieve a guaranteed response time; for this you must use the pre-emptive scheduling associated with threads 1 , which is described in Chapter 10. If you have a large number of active objects in a single thread which complete often, they ”compete” for their event handler to be run by the active scheduler. If some of the active objects have high priorities and receive frequent completion events, those with lower priorities wait indefinitely until the active scheduler can call their RunL() methods. In effect, it’s possible to ”hang” lower-priority active objects by adding them to an active scheduler upon which a number of high-priority active objects are completing. 1 The new hard real-time kernel in Symbian OS 8.0 can commit to a particular response time. On earlier versions of Symbian OS, the kernel has soft real-time capabilities and cannot make such guarantees. RESPONSIBILITIES OF AN ACTIVE OBJECT 131 An active object’s priority is only an indication of the order in which the active scheduler performs lookup and event-handling. It does not reflect an ability to pre-empt other active objects. 9.2 Responsibilities of an Active Object Figure 9.1 illustrates the roles and actions of the active scheduler, active object and the asynchronous service provider. It extends Figure 8.1. Executable Active Object Active Scheduler Asynchronous Service Provider Create and Install the Active Scheduler Create Active Object (with appropriate priority) and add it to the Active Scheduler Issue a request to the Active Object Start the Active Scheduler if it is not already started Make a request to the Asynchronous Service Provider, passing in iStatus Call SetActive() RunL() handles the completed event and resubmits another request or stops the Active Scheduler CActiveScheduler::Stop() Sets iStatus=KRequestPending and starts the service Service completes and uses RequestComplete() to notify the Active Scheduler (by signalling its thread semaphore) and to post a completion result PROCESS OR THREAD BOUNDARY Wait Loop CActiveScheduler::Start() Calls RunL() on the Active Object with iStatus!=KRequestPen ding and iActive=ETrue Figure 9.1 Roles and actions of the active scheduler, an active object and an asynchronous service provider 132 ACTIVE OBJECTS UNDER THE HOOD It looks complex, but I’ll explain how it all fits together throughout this chapter and you’ll probably want to refer back to it later. The following list summarizes the responsibilities of an active object: • As I described in Chapter 8, the priority of an active object must be set on construction. The priority generally defaults to EPriorityStan- dard (=0,fromclassCActive)orEActivePriorityDefault (=0 if using the TActivePriority enumeration defined in coemain.h for use with application code). This is the standard priority for an active object and should be used unless there is a good reason to set its priority to some other value, for example to EActivePriority- WsEvents (=100) for handling user input responsively. • An active object provides at least one method for clients to initiate requests to its encapsulated asynchronous service provider. The active object always passes its own iStatus object to the asynchronous function, so does not need to include a TRequestStatus reference among the parameters to the request issuer method unless it is acting as a secondary provider of asynchronous services. • After submitting a request to an asynchronous service provider, the active object must call SetActive() upon itself. This sets the iActive flag, which indicates an outstanding request. This flag is used by the active scheduler upon receipt of an event and by the base class upon destruction, to determine whether the active object can be removed from the active scheduler. • An active object should only submit one asynchronous request at a time. The active scheduler has no way of managing event-handling for multiple requests associated with one active object. • An active object should pass its iStatus object to an asynchronous service function. It should not reuse that object until the asynchronous function has completed and been handled. The active scheduler inspects the TRequestStatus of each active object to determine its completion state and the event-handling code uses the value it contains to ascertain the completion result of the function. • An active object must implement the pure virtual methods RunL() and DoCancel() declared in the CActive base class. Neither method should perform lengthy or complex processing, to avoid holding up event handling in the entire thread. This is particularly important in GUI applications where all user interface code runs in the same thread. If any single RunL() is particularly lengthy, the user interface will be unresponsive to input and will ”freeze” until that event handler has completed. RESPONSIBILITIES OF AN ASYNCHRONOUS SERVICE PROVIDER 133 • An active object must ensure that it is not awaiting completion of a pending request when it is about to be destroyed. As destruction removes it from the active scheduler, later completion will generate an event for which there is no associated active object. To prevent this, Cancel() should be called in the destructor of an active object. The destructor of the CActive base class checks that there is no outstanding request before removing the object from the active scheduler and raises an E32USER–CBASE 40 panic if there is, to highlight the programming error. The base class destructor cannot call Cancel() itself because that method calls the derived class implementation of DoCancel() – and, of course, C++ dictates that the derived class has already been destroyed by the time the base class destructor is called. • Objects passed to the asynchronous service provider by the issuer methods must have a lifetime at least equal to the time taken to complete the request. This makes sense when you consider that the provider may use those objects until it is ready to complete the request, say if it is retrieving and writing data to a supplied buffer. This requirement means that parameters supplied to the provider should usually be allocated on the heap (very rarely, they may be on a stack frame that exists for the lifetime of the entire program). In general, parameters passed to the asynchronous service provider should belong to the active object, which is guaranteed to exist while the request is outstanding. • If a leave can occur in RunL(), the class should override the default implementation of the virtual RunError() method to handle it. RunError() was added to CActive in Symbian OS v6.0 to han- dle any leaves that occur in the RunL() event handler. If a leave occurs, the active scheduler calls the RunError() method of the active object, passing in the leave code. RunError() should return KErrNone to indicate that it has handled the leave, say by cleaning up or resetting the state of the active object. The default implementa- tion, CActive::RunError(), does not handle leaves and indicates this by simply returning the leave code passed in. 9.3 Responsibilities of an Asynchronous Service Provider An asynchronous service provider has the following responsibilities: • Before beginning to process each request, the provider must set the incoming TRequestStatus value to KRequestPending to indicate to the active scheduler that a request is ongoing. 134 ACTIVE OBJECTS UNDER THE HOOD • When the request is complete, the provider must set the TRe- questStatus value to a result code other than KRequestPending by calling the appropriate RequestComplete() method from the RThread or User class. • The asynchronous service provider must only call Request- Complete() once for each request. This method generates an event in the requesting thread to notify it of completion. Multiple comple- tion events on a single active object result in a stray signal panic. Completion may occur normally, because of an error condition or because the client has cancelled an outstanding request. If the client calls Cancel() on a request after it has completed, the asynchronous service provider must not complete it again and should simply ignore the cancellation request. This is discussed further in Sections 9.8 and 9.9. • The provider must supply a corresponding cancellation method for each asynchronous request; this should complete an outstanding request immediately , posting KErrCancel into the TRequest- Status object associated with the initial request. 9.4 Responsibilities of the Active Scheduler The active scheduler has the following responsibilities: • Suspending the thread by a call to User::WaitForAnyRequest(). When an event is generated, it resumes the thread and inspects the list of active objects to determine which has issued the request that has completed and should be handled. • Ensuring that each request is handled only once. The active scheduler should reset the iActive flag of an active object before calling its handler method. This allows the active object to issue a new request from its RunL() event handler, which results in SetActive() being called (which would panic if the active object was still marked active from the previous request). • Placing a TRAP harness around RunL() calls to catch any leaves occurring in the event-handling code. If the RunL() call leaves, the active scheduler calls RunError() on the active object ini- tially. If the leave is not handled there, it passes the leave code to CActiveScheduler::Error(), described in more detail shortly. • Raising a panic (E32USER–CBASE 46) if it receives a ”stray signal”. This occurs when the request semaphore has been notified of an event, but the active scheduler cannot find a ”suitable” active object NESTING THE ACTIVE SCHEDULER 135 (with iActive set to ETrue and a TRequestStatus indicating that it has completed). 9.5 Starting the Active Scheduler Once an active scheduler has been created and installed, its event processing wait loop is started by a call to the static CActive- Scheduler::Start() method. Application programmers do not have to worry about this, since the CONE framework takes care of managing the active scheduler. If you are writing server code, or a simple console application, you have to create and start the active scheduler for your server thread, which can be as simple as follows: CActiveScheduler* scheduler = new(ELeave) CActiveScheduler; CleanupStack::PushL(scheduler); CActiveScheduler::Install(scheduler); The call to Start() enters the event processing loop and does not return until a corresponding call is made to CActive- Scheduler::Stop(). Thus, before the active scheduler is started, there must be at least one asynchronous request issued, via an active object, so that the thread’s request semaphore is signaled and the call to User::WaitForAnyRequest() completes. If no request is outstand- ing, the thread simply enters the wait loop and sleeps indefinitely. As you would expect, the active scheduler is stopped by a call to CActiveScheduler::Stop(). When that enclosing function returns, the outstanding call to CActiveScheduler::Start() also returns. Stopping the active scheduler breaks off event handling in the thread, so it should only be called by the main active object controlling the thread. 9.6 Nesting the Active Scheduler I’ve already noted that an event-handling thread has a single active scheduler. However, it is possible, if unusual, to nest other calls to CActiveScheduler::Start(), say within a RunL() event-handling method. The use of nested active scheduler loops is generally discour- aged but can be useful if a call should appear to be synchronous, while actually being asynchronous (”pseudo-synchronous”). A good example is a RunL() event handler that requires completion of an asynchronous request to another active object in that thread. The RunL() call cannot be pre-empted, so it must instead create a nested wait loop by calling CActiveScheduler::Start(). This technique is used in modal Uikon ”waiting” dialogs. 136 ACTIVE OBJECTS UNDER THE HOOD Each call to CActiveScheduler::Start() should be strictly matched by a corresponding call to CActiveScheduler::Stop() in an appropriate event handler. Before employing such a technique you must be careful to test your code thoroughly to ensure that the nesting is controlled under both normal and exceptional conditions. The use of nested active scheduler event-processing loops can introduce subtle bugs, particularly if more than one nested loop is used concurrently in the same thread. For example, if a pair of independent components both nest active scheduler loops, their calls to Start() and Stop() must be carefully interleaved if one component is to avoid stopping the loop of the other’s nested active scheduler. The complexity that results from nesting active scheduler processing loops means that Symbian does not recommend this technique. However, where the use of nested active scheduler loops is absolutely unavoid- able, releases of Symbian OS from v7.0s onwards have introduced the CActiveSchedulerWait class to provide nesting ”levels” that match active scheduler Stop() calls to the corresponding call to Start(). 9.7 Extending the Active Scheduler CActiveScheduler is a concrete class and can be used ”as is”, but it can also be subclassed. It defines two virtual functions which may be extended: Error() and WaitForAnyRequest(). By default, the WaitForAnyRequest() function simply calls User::WaitForAnyRequest(), but it may be extended, for example to perform some processing before or after the wait. If the function is re-implemented, it must either call the base class function or make a call to User::WaitForAnyRequest() directly. I described earlier how if a leave occurs in a RunL() event handler, the active scheduler passes the leave code to the RunError() method of the active object. If this method cannot handle the leave, it returns the leave code and the active scheduler passes it to its own Error() method. By default, this raises a panic (E32USER-CBASE 47), but it may be extended in a subclass to handle the error, for example by calling an error resolver to obtain the textual description of the error and displaying it to the user or logging it to file. If your active object code is dependent upon particular specializations of the active scheduler, bear in mind that it will not be portable to run in other threads managed by more basic active schedulers. Furthermore, any additional code added to extend the active scheduler should be straightforward and you should avoid holding up event-handling in the entire thread by performing complex or slow processing. CANCELLATION 137 9.8 Cancellation Every request issued by an active object must complete exactly once. It can complete normally or complete early as a result of an error or a call to Cancel(). Let’s first examine what happens in a call to CActive::Cancel() and return to the other completion scenar- ios later. CActive::Cancel() first determines if there is an outstanding request and, if so, it calls the DoCancel() method, a pure vir- tual function in CActive, implemented by the derived class (which should not override the non-virtual base class Cancel() method). DoCancel() does not need to check if there is an outstanding request; if there is no outstanding request, Cancel() does not call it. The encapsu- lated asynchronous service provider should provide a method to cancel an outstanding request and DoCancel() should call this method. DoCancel() can include other processing, but it should not leave or allocate resources and it should not carry out any lengthy operations. This is because Cancel() is itself a synchronous function which does not return until both DoCancel() has returned and the original asyn- chronous request has completed. That is, having called DoCancel(), CActive::Cancel() then calls User::WaitForRequest(), pass- ing in a reference to its iStatus member variable. It is blocked until the asynchronous service provider posts a result (KErrCancel) into it, which should happen immediately, as described above. The cancellation event is thus handled by the Cancel() method of the active object rather than by the active scheduler. Finally, Cancel() resets the iActive member of the active object to reflect that there is no longer an asynchronous request outstanding. The Cancel() method of the CActive base class performs all this generic cancellation code. When implementing a derived active object class, you only need to implement DoCancel() to call the appropriate cancellation function on the asynchronous service provider and perform any cleanup necessary. You most certainly should not call User::WaitForRequest(), since this will upset the thread semaphore count. Internally, the active object must not call the pro- tected DoCancel() method to cancel a request; it should call CActive::Cancel(), which invokes DoCancel() and handles the resulting cancellation event. When an active object request is cancelled by a call to Cancel(),theRunL() event handler does not run. This means that any post-cancellation cleanup must be performed in DoCancel() rather than in RunL(). 138 ACTIVE OBJECTS UNDER THE HOOD 9.9 Request Completion At this point, we can summarize the ways in which a request issued from an active object to an asynchronous service provider can com- plete: • The request is issued to the asynchronous service provider by the active object. Some time later, the asynchronous service provider calls User::RequestComplete() which generates a completion event and passes back a completion result. The active scheduler detects the completion event, resumes the thread and initiates event handling on the highest priority active object that has iActive set to ETrue and iStatus set to a value other than KRequestPending. This is a normal case, as described in the walkthrough above, although the completion result may not reflect a successful outcome. • The asynchronous request cannot begin, for example if invalid parameters are passed in or insufficient resources are available. The asynchronous service provider should define a function that neither leaves nor returns an error code (it should typically return void). Thus, under these circumstances, the request should complete immediately, posting an appropriate error into the TRequestStatus object passed into the request function. • The request is issued to the asynchronous service provider and Cancel() is called on the active object before the request has completed. The active object calls the appropriate cancellation func- tion on the asynchronous service provider, which should terminate the request immediately. The asynchronous service provider should com- plete the request with KErrCancel as quickly as possible, because CActive::Cancel() blocks until completion occurs. • The request is issued to the asynchronous service provider and Cancel() is called on the active object some time after the request has completed. This occurs when the completion event has occurred but is yet to be processed by the active scheduler. The request appears to be outstanding to the active object framework, if not to the asyn- chronous service provider, which simply ignores the cancellation call. CActive::Cancel() discards the normal completion result. 9.10 State Machines An active object class can be used to implement a state machine to per- form a series of actions in an appropriate sequence, without requiring [...]... EPriorityAbsoluteLow=200, EPriorityAbsoluteBackground=300, EPriorityAbsoluteForeground=400, EPriorityAbsoluteHigh =50 0 }; enum TProcessPriority { EPriorityLow= 150 , EPriorityBackground= 250 , EPriorityForeground= 350 , EPriorityHigh= 450 , EPriorityWindowServer= 650 , EPriorityFileServer= 750 , EPriorityRealTimeServer= 850 , EPrioritySupervisor= 950 }; The following values are relative thread priorities: EPriorityMuchLess,... made for the new hard real-time kernel delivered in releases of Symbian OS v8.0 I’ll identify the main differences as I come to them Most notably, a number of the functions are now restricted for use on threads in the current process only, whereas previous versions of Symbian OS allowed one thread to manipulate any other thread in the system, even those in other processes The changes to Symbian OS v8.0... descriptor The maximum length of the target descriptor can be determined before writing by a call to RThread::GetDesMaxLength() 5 This is true for EKA2 and previous releases of Symbian OS running on target hardware However, the Symbian OS Windows emulator for releases prior to EKA2 does not protect the address spaces of Symbian OS processes The threads share writable memory, which means that each emulated... press, Symbian identifies the hard real-time kernel as ”EKA2” – which is a historical reference standing for ”EPOC2 2 Symbian OS was previously known as EPOC, and earlier still, EPOC32 CLASS RThread 153 Kernel Architecture 2” – and refers to the kernel of previous releases as EKA1 Throughout this chapter, and the rest of the book, I’ll use this nomenclature to distinguish between versions of Symbian OS. .. 8 and 9 discussed the role of active objects in multitasking code on Symbian OS Active objects are preferred to threads for this role because they were designed specifically to suit the resource-limited hardware upon which Symbian OS runs Multithreaded code has significantly higher run-time requirements compared to active objects: for example, a context switch between threads may be an order of magnitude... background thread To be suitable, the task must be divisible into multiple short increments, for example, preparing data for printing, performing background recalculations and compacting the database The increments are performed in the event handler of the active object For this reason, they must be short enough for event handling in the thread to continue to be responsive, because RunL() cannot be pre-empted... synchronization objects and, on Symbian OS, the overhead of running multiple threads is significantly higher than that for multiple active objects running in a single thread You should prefer low-priority active objects for long-running tasks, except for cases where the task cannot be split into convenient increments The next chapter illustrates how to use a separate thread to perform a long-running task which... CActive-derived class for running incremental task steps It uses a timer object to generate regular timer events, handling a single step of the task for each timer event CPeriodic is useful for performing regular task steps such as flashing a cursor or for controlling time-outs Like CIdle, CPeriodic is initialized with a priority value and the task is initiated by a call to Start(), passing settings for the timer... value specified For example, if the process has TProcessPriority::EPriorityHigh (= 450 ), and SetPriority() is called with TThreadPriority of EPriorityMuchLess, the absolute priority of the thread will be STOPPING A RUNNING THREAD 157 450 − 20 = 430 If, for the same process, TThreadPriority:: EPriorityMuchMore is passed to SetPriority() instead, the absolute priority of the thread will be 450 + 20 = 470... described above It is possible to receive notification when a thread dies by making a call to RThread::Logon() on a valid thread handle The manner and reason for thread termination can also be determined from the RThread handle of an expired thread by calling ExitType(), ExitReason() and ExitCategory() 162 10.4 SYMBIAN OS THREADS AND PROCESSES Inter-Thread Data Transfer On Symbian OS, you can’t transfer . Error() and WaitForAnyRequest(). By default, the WaitForAnyRequest() function simply calls User::WaitForAnyRequest(), but it may be extended, for example to perform some processing before or after. incre- ments, for example, preparing data for printing, performing background recalculations and compacting the database. The increments are per- formed in the event handler of the active object. For this. that it is ”holding up” the handler for the higher-priority object. On Symbian OS, you cannot use active object priorities to achieve a guaranteed response time; for this you must use the pre-emptive scheduling

Ngày đăng: 14/08/2014, 12:20

Mục lục

    9 Active Objects under the Hood

    9.2 Responsibilities of an Active Object

    9.3 Responsibilities of an Asynchronous Service Provider

    9.4 Responsibilities of the Active Scheduler

    9.5 Starting the Active Scheduler

    9.6 Nesting the Active Scheduler

    9.7 Extending the Active Scheduler

    10 Symbian OS Threads and Processes

    10.3 Stopping a Running Thread

    11 The Client– Server Framework in Theory

Tài liệu cùng người dùng

Tài liệu liên quan