Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 57
Текст из файла (страница 57)
Note that the synchronous backgroundoperation isn’t shown because it’ll be implemented in just the same wayas this episode.class CRequiredSyncEpisode: public CBase, public MEpisode{public:static CRequiredSyncEpisode* NewLC();public: // From MEpisodeEPISODES299virtual void MeExecute(MEpisodeCompletion& aEpisodeObserver);virtual void MeExecuteCancel();...};The MeExecute() function is conceptually very simple. All it does issynchronously perform the operations that have been grouped into thisepisode and signal to the serializer when it’s done. Note that this functionuses Escalate Errors (see page 32) to resolve errors but it doesn’t do sousing the Leave mechanism.
Instead all errors are passed up to the clientthrough the MecEpisodeComplete() function so that the serializeronly has the one error path to deal with.void CRequiredSyncEpisode::MeExecute(MEpisodeCompletion& aEpisodeObserver){TInt err = KErrNone;// Perform synchronous actions// Complete client immediatelyaEpisodeObserver.MecEpisodeComplete(*this, err);}The implementation of MeExecuteCancel() does not need to perform any actions as there is no opportunity to cancel this episode:void CRequiredSyncEpisode::MeExecuteCancel(){}Here’s the definition of an episode that derives from CActive to allowit to perform its execution asynchronously:class CRequiredAsyncEpisode : public CActive, public MEpisode{public:static CRequiredAsyncEpisode* NewLC();public: // From MEpisodevirtual void MeExecute(MEpisodeCompletion& aEpisodeObserver);virtual void MeExecuteCancel();public: // From CActivevirtual void RunL();virtual TInt RunError(TInt aError);virtual void DoCancel();private:void NotifyEpisodeComplete(TInt aError);...private:MEpisodeCompletion* iEpisodeObserver;};300OPTIMIZING EXECUTION TIMENote that the active object priority given to an asynchronous episodedepends on whether it’s a required episode, when you’d expect it to havea high priority, or whether it’s a background episode, when you’d expectit to have a low priority perhaps even EPriorityIdle so it only runswhen the thread has nothing else to do.The asynchronous implementation of the MeExecute() methodneeds to remember the aEpisodeObserver passed as a parameterto allow it to be completed once the episode has finished.
Other thanthat it simply needs to perform its asynchronous operation:void CRequiredAsyncEpisode::MeExecute(MEpisodeCompletion&aEpisodeObserver){iEpisodeObserver = &aEpisodeObserver;IssuesAsyncRequest(iStatus);SetActive();}Once the asynchronous request has completed, RunL() is called tohandle the result. This might be to perform further synchronous operationsor simply to notify the completion of the episode:void CRequiredAsyncEpisode::RunL(){User::LeaveIfError(iStatus);// Perform further synchronous operations as neededTInt err = KErrNone;...// Operations are all done so tell the serializerNotifyEpisodeComplete(err);}We use the RunError() method to provide our error handling whichis simply to notify the serializer that the episode has completed thoughyou could try to resolve the error by retrying an operation, for example,before handing the error on.TInt CRequiredAsyncEpisode::RunError(TInt aError){NotifyEpisodeComplete(aError);return KErrNone;}The NotifyEpisodeComplete() function just helps ensure that wemaintain the following class invariant: that iEpisodeObserver is notNULL only when the episode is executing its operations:EPISODES301void CRequiredAsyncEpisode::NotifyEpisodeComplete(TInt aError){iEpisodeObserver->MecEpisodeComplete(*this, aError);iEpisodeObserver = NULL;}Finally, we need to ensure that the serializer can correctly cancelthe episode.
We assume that you will also provide an implementationof your DoCancel() function3 to cancel your implementation-specificasynchronous operation.void CRequiredAsyncEpisode::MeExecuteCancel(){// The active object cancel is used to cancel the current asynchronous// operation that the episode is performingCancel();// Notify the serializer that the cancel is completeif (iEpisodeObserver){NotifyEpisodeComplete(KErrCancel);}}ConsequencesPositives• The task is executed more quickly because all of the operations notimmediately necessary to complete the task are delayed until after thetask appears to have completed.• The task is easier to maintain since each episode is treated in exactlythe same way and there is no possibility of treating one episode differently from another which might otherwise catch out future developersof your component. One example of this is the way that synchronousand asynchronous episodes appear to work in the same way to theserializer which allows you to easily convert episodes between thesetwo forms of execution.
In addition, the layout of the task is very clear,which helps maintainers to understand what it is doing.• This solution increases the flexibility you have in changing the orderin which you perform episodes by having a single place in which theirorder is set up. This allows you to easily change the order or even addfurther episodes in future. Dependencies between the episodes areeasily controlled and are more likely to be respected correctly becausethey are executed in a linear order and do not interfere with eachother.3 Requiredas part of CRequiredAsyncEpisode being an active object.302OPTIMIZING EXECUTION TIMENegatives• Additional development time is needed upfront to correctly understand the way the task operates and how it might be divided intoepisodes.• You introduce a potentially complicated situation in which a clientmay request a new task to be performed whilst the backgroundepisodes of the previous task are still executing.
For some tasks, thiscan be a significant problem that needs to be solved.• Circular dependencies between episodes can’t be expressed by thelinear ordering of them imposed by the serializer. This pattern is onlyable to handle circular dependencies by encapsulating them within asingle episode. Assuming, of course, that you can’t find some way tobreak the circular dependency altogether.• Episodes cannot be immediately parallelized which might preventfurther execution time optimizations. However, if you do need toparallelize episodes then this pattern provides a starting point for adesign that can cope with this.• This solution adds some overhead to the processing of the task.
Thisis normally a relatively small number of additional CPU cycles aswell as code size to organize the episodes. For a non-trivial overalltask divided up into a small number of episodes, this isn’t usuallysignificant.Example ResolvedHere, we consider the simplified problem described earlier to avoid anyunrelated complexity.4 In applying this pattern to the media player initialization, the first decision is to choose the episodes; since the initializationof the application splits up nicely into four top-level operations, we usethem as our episodes:• reading data stored by the end user• loading the most recent track list shown to the end user• playing the last track selected by the end user• constructing and displaying the UI to the end user.If we were to simply execute them in the order above, we wouldn’tsee any optimization so we choose to delay the playing of the last trackselected by the end user until after we’ve shown the UI to the end user.4 For the complete solution in OggPlay, please look at the source code given by [Wildenet al., 2003].EPISODES303Unfortunately, we can’t delay the other episodes because they retrieveinformation used when displaying the UI.Having chosen the episodes and their order, we represent this in thecode within the following function.
Note that this function is called whenCOggPlaySerializer is created by COggPlayApplication:void COggPlaySerializer::ConstructL(){CUserDataEpisode* ude = CUserDataEpisode::NewLC();iEpisodes.AppendL(ude);CleanupStack::Pop(ude);CTrackListEpisode* tle = CTrackListEpisode::NewLC();iEpisodes.AppendL(tle);CleanupStack::Pop(tle);CMainViewEpisode* mve = CMainViewEpisode::NewLC();iEpisodes.AppendL(mve);CleanupStack::Pop(mve);iFirstBackgroundEpisode = iEpisodes.Count();CMusicPlayerEpisode* mpe = CMusicPlayerEpisode::NewLC();iEpisodes.AppendL(mpe);CleanupStack::Pop(mpe);}The COggPlaySerializer class is otherwise constructed exactly asgiven in the pattern above.When constructing the individual episodes we need to take intoaccount whether or not they need to be performed synchronously orasynchronously. In the example here, only the reading in of the end userdata needs to be done asynchronously so this is the only episode that isimplemented as an active object.The following code shows possible implementations for the Execute() methods for the four episodes described above.
The implementation of the register methods such as RegisterUserData() are notshown but would be responsible for transferring ownership of the createdobjects to the appropriate part of the application.void CUserDataEpisode::Execute(MEpisodeCompletion& aEpisodeObserver){// Remember the requesting callback to complete later oniEpisodeObserver = aEpisodeObserver;// Load end user dataTRAPD(err, iUserData = CUserData::NewL());if (err == KErrNone){iUserData->ReadData(iStatus);304OPTIMIZING EXECUTION TIMESetActive();}else{// An error occurred, so end the episode immediatelyNotifyEpisodeComplete(err);}}// since CUserDataEpisode derives from CActivevoid CUserDataEpisode::RunL(){User::LeaveIfError(iStatus);// Success so transfer ownership of end user dataRegisterUserData(userData);// End the episode successfullyNotifyEpisodeComplete(iStatus.Int());}void CTrackListEpisode::Execute(MEpisodeCompletion& aEpisodeObserver){// Load the track listCTrackList* trackList = NULL;TRAPD(err, trackList = CTrackList::NewL());if(err == KErrNone){RegisterTrackList(trackList); // Transfer ownership of track list}// End the episode synchronouslyaEpisodeObserver->EpisodeComplete(*this, err);}void CMainViewEpisode::Execute(MEpisodeCompletion& aEpisodeObserver){// Create the main viewCMainView* mainView = NULL;TRAPD(err, mainView = CMainView::NewL());if(err == KErrNone){mainView->Display(); // Initialization done so display the main viewRegisterMainView(mainView); // Transfer ownership of the main view}// End the episode synchronouslyaEpisodeObserver->EpisodeComplete(*this, err);}void CMusicPlayerEpisode::Execute(MEpisodeCompletion& aEpisodeObserver){CMusicPlayer* musicPlayer = NULL;TRAPD(err, musicPlayer = CMusicPlayer::NewL());if(err == KErrNone){musicPlayer->Play();EPISODES305// Transfer ownership of the music playerRegisterMusicPlayer(musicPlayer);}// End the episode synchronouslyaEpisodeObserver->EpisodeComplete(*this, err);}Other Known Uses• Application Splash ScreensIt is common practice for applications to use a splash screen in thesame way as used by OggPlay to give the end user near instantfeedback that the application is loading.