Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

LONG-RUNNING TASKS

143

In effect, CStateMachine maintains a series of outstanding requests to the service provider in RunL(), rather than making a single call. This example is quite straightforward because there are only three active states, but the code logic here can potentially be far more complex. The DoCancel() method of CStateMachine must also have some state-related logic so that the correct method on CServiceProvider is cancelled. The states and transitions are illustrated in Figure 9.2.

Send TranslatedData()

Call CServiceProvider::GetData()

1. EIdle

2. EGet

Cancel()or

RunError()

Cancel(),

RunError() or

RunL() calls

normal completion

RunError() CServiceProvider::

TranslateData()

(self completes)

4. ESend

3. ETranslate

RunL() calls

CServiceProvider::SendData()

Figure 9.2 Internal states of CStateMachine

In this example the service provider functions called by the state machine are a mixture of synchronous (TranslateData()) and asynchronous (GetData() and SendData()) functions. The synchronous method uses self-completion to simulate an asynchronous completion event, which is discussed further in the following section.

9.11 Long-Running Tasks

Besides encapsulating asynchronous service providers, active objects can also be used to implement long-running tasks which would otherwise need to run in a lower-priority 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 once it is running.

The active object should be assigned a low priority such as

CActive::TPriority::EPriorityIdle (=-100), which determines that a task increment only runs when there are no other events to

144

ACTIVE OBJECTS UNDER THE HOOD

handle, i.e. in idle time. If the task consists of a number of different steps, the active object must track the progress as series of states, implementing it using a state machine as described above.

The active object drives the task by generating its own events to invoke its event handler. Instead of calling an asynchronous service provider, it completes itself by calling User::RequestComplete() on its own iStatus object and calls SetActive() on itself so the active scheduler calls its event handler. In this way it continues to resubmit requests until the entire task is complete. A typical example is shown in the sample code, where I’ve shown all the relevant methods in the class declarations but only the implementations relevant to this discussion. I’ve also omitted error handling for clarity:

// This class has no dependence on the active object framework class CLongRunningCalculation : public CBase

{

public:

static CLongRunningCalculation* NewL();

TBool StartTask();

// Initialization before starting the task

TBool DoTaskStep();

// Performs a short task step

void EndTask();

// Destroys intermediate data

...

 

};

 

TBool CLongRunningCalculation::DoTaskStep() {// Do a short task step, returning

//ETrue if there is more of the task to do

//EFalse if the task is complete

... // Omitted

}

_LIT(KExPanic, "CActiveExample");

class CBackgroundRecalc : public CActive

{

public:

... // NewL(), destructor etc are omitted for clarity public:

void PerformRecalculation(TRequestStatus& aStatus); protected:

CBackgroundRecalc(); void ConstructL(); void Complete(); virtual void RunL();

virtual void DoCancel(); private:

CLongRunningCalculation* iCalc;

TBool iMoreToDo;

TRequestStatus* iCallerStatus; // To notify caller on completion };

CBackgroundRecalc::CBackgroundRecalc()

: CActive(EPriorityIdle) // Low priority task { CActiveScheduler::Add(this); }

LONG-RUNNING TASKS

145

//Issues a request to initiate a lengthy task

void CBackgroundRecalc::PerformRecalculation(TRequestStatus& aStatus)

{

iCallerStatus = &aStatus; *iCallerStatus = KRequestPending;

__ASSERT_ALWAYS(!IsActive(), User::Panic(KExPanic, KErrInUse)); iMoreToDo = iCalc->StartTask(); // iCalc initializes the task

Complete();

// Self-completion to generate an event

}

 

void CBackgroundRecalc::Complete()

{// Generates an event on itself by completing on iStatus TRequestStatus* status = &iStatus; User::RequestComplete(status, KErrNone);

SetActive();

}

// Performs the background task in increments void CBackgroundRecalc::RunL()

{// Resubmit request for next increment of the task or stop if (!iMoreToDo)

{// Allow iCalc to cleanup any intermediate data iCalc->EndTask();

// Notify the caller User::RequestComplete(iCallerStatus, iStatus.Int());

}

else

{// Submit another request and self-complete to generate event iMoreToDo = iCalc->DoTaskStep();

Complete();

}

}

void CBackgroundRecalc::DoCancel()

{// Give iCalc a chance to perform cleanup if (iCalc)

iCalc->EndTask();

if (iCallerStatus) // Notify the caller User::RequestComplete(iCallerStatus, KErrCancel);

}

If you are designing an API for a long-running task, to make it useful with this pattern, it is a good idea to provide methods which split the task into increments, as in the example above. StartTask(), DoTaskStep() and EndTask() perform small, discrete chunks of the task and can be called directly by the RunL() method of the low-priority active object. The API can then also be reused by code which implements long-running tasks differently, since it will have no dependence on the active object itself. Besides portability, distancing the active object model makes it more straightforward to focus on the implementation of the long-running task itself.

Of course, one disadvantage of this approach is that some tasks cannot be broken down into short steps. Another is that if you implement a

146

ACTIVE OBJECTS UNDER THE HOOD

number of low-priority active objects for long-running tasks in the same thread, you will need to work out how best to run them together and write the necessary low-priority scheduling code yourself.

The use of a background thread for long-running tasks is fairly straightforward. The code for the task can be written without worrying about yielding the CPU, since the kernel schedules it when no higher-priority thread needs to run, pre-empting it again when a more important thread needs access to system resources. However, as with all multi-threaded code, any shared resources must be protected against illegal concurrent access by 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 is wrapped with code for an active object.

9.12Class CIdle

CIdle derives from CActive and is a useful class which wraps the active object basics such as implementing RunL() and DoCancel(). The wrapper allows you to focus solely on implementing the code to run the incremental task without worrying about the active object code.

class CIdle : public CActive

{

public:

IMPORT_C static CIdle* New(TInt aPriority);

IMPORT_C static CIdle* NewL(TInt aPriority);

IMPORT_C CIdle();

IMPORT_C void Start(TCallBack aCallBack); protected:

IMPORT_C CIdle(TInt aPriority);

IMPORT_C void RunL();

IMPORT_C void DoCancel(); protected:

TCallBack iCallBack;

};

The CIdle object should be created with a low or idle priority and the long-running task initiated by a call to Start(), passing a callback function to perform an increment of the task. The TCallback object encapsulates a pointer to a callback function which takes an argument of type TAny* and returns an integer. The callback function manages the task increments and can be a local or a static member function. It should keep track of the task progress, returning ETrue if further steps are

CLASS CIdle

147

necessary and EFalse when it is complete. In much the same way as the incremental task shown above, the RunL() event handler which calls the TCallback object is only called during idle time. Furthermore, it will not be pre-empted while it is running. As long as the callback function indicates that further steps of the task are required, CIdle::RunL() resubmits requests by completing on its own iStatus object and calling

SetActive().

Here’s some example code for another background recalculation task class. In fact, I’ve slightly reworked the class from the example above to be driven by CIdle. You’ll notice that there’s no ”boilerplate” active object code required, unlike the code in the CBackgroundRecalc class, because CIdle provides that functionality.

class CLongRunningCalculation : public CBase

{

public:

static CLongRunningCalculation* NewLC(TRequestStatus& aStatus);

protected:

static TInt TaskStep(TAny* aLongCalc); // Callback function

protected:

 

void StartTask();

// Initialization before starting the task

TBool DoTaskStep();

// Does a step of the task

void EndTask();

// Destroys intermediate data

protected:

 

CLongRunningCalculation(TRequestStatus& aStatus); private:

...

TBool iMoreToDo; // Flag tracks the progress of the task // To notify the caller when the calc is complete TRequestStatus* iCallerStatus;

};

CLongRunningCalculation* CLongRunningCalculation::NewLC(TRequestStatus& aStatus)

{

CLongRunningCalculation* me = new (ELeave)

CLongRunningCalculation(aStatus);

CleanupStack::PushL(me); // ... 2nd phase construction code omitted return (me);

}

CLongRunningCalculation::CLongRunningCalculation(TRequestStatus&

aStatus)

: iCallerStatus(&aStatus) {}

TBool CLongRunningCalculation::DoTaskStep()

{// Does a short task step, returning ETrue if it has more to do

... // Omitted

}

void CLongRunningCalculation::StartTask() {// Prepares the task

iMoreToDo = ETrue;

}

148 ACTIVE OBJECTS UNDER THE HOOD

// Error handling omitted

void CLongRunningCalculation::EndTask()

{// Performs cleanup when the task has completed ASSERT(!iMoreToDo);

...

User::RequestComplete(iCallerStatus, KErrNone);

}

TInt CLongRunningCalculation::TaskStep(TAny* aLongCalc)

{

ASSERT(aLongCalc);

CLongRunningCalculation* longCalc =

static_cast<CLongRunningCalculation*>(aLongCalc); if (!longCalc->iMoreToDo)

longCalc->EndTask(); else

longCalc->iMoreToDo = longCalc->DoTaskStep();

return (longCalc->iMoreToDo);

}

Code which uses the idle object will look something like the following example, which creates the CIdle object and a CLongRunningCalculation object that drives the task:

CIdle* idle = CIdle::NewL(EPriorityIdle);

CleanupStack::PushL(idle);

// Create the object that runs the task, passing in a TRequestStatus& CLongRunningCalculation* longCalc =

CLongRunningCalculation::NewLC(iStatus);

TCallBack callback(CLongRunningCalculation::TaskStep, longCalc); idle->Start(callback);

... // Completion of the task completes iStatus

9.13Class CPeriodic

CPeriodic is another useful 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 as well as a callback to perform increments of the task. When the timer period elapses, the CPeriodic object generates an event that is detected by the active scheduler. When there are no active objects of higher priority requiring event handling, the active scheduler calls the RunL() event