Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 29
Текст из файла (страница 29)
However, a slightlysimpler form of this pattern is when the active object only existswhilst a single asynchronous request is outstanding. By making thisdesign choice,you can simplify the active object and its use by theclient as you are then able to implement the active object so that itmakes the asynchronous request when it is constructed. The clientthen doesn’t need to separately remember to tell the active object tostart the asynchronous request and nor does it need to worry aboutwhether the active object has a request outstanding; if it exists thena request is pending.
To maintain this design, once a request hasbeen completed the client must ensure it deletes the associated activeobject.• Asynchronous Event MixinsThis pattern is often coupled with Event Mixin (see page 93) to support asynchronous delivery of event signals within a single thread. Theevent generator owns an active object, such as CAsyncCallback,which it informs of event signals. This active object completes itselfso that its RunL() is executed at the next opportunity to actually sendthe event signal by synchronously calling the appropriate event mixinfunction on the event consumer. Using Event Mixin (see page 93)helps decouple the event generators from event mixins and provides abetter encapsulated mechanism than using Request Completion (seepage 104) directly.ACTIVE OBJECTS147References• Asynchronous Controller (see page 148) extends this pattern to dealwith tasks that require multiple asynchronous requests to be completedbefore they’re done.• Client–Server (see page 182) is implemented using this pattern.• Request Completion (see page 104) is used to implement this pattern.• Proactor [Harrison et al., 2000] is a pattern that integrates the demultiplexing of asynchronous completion events and the dispatchingof their corresponding event handlers.• Active Object [Lavender and Schmidt, 1996] is a pattern that addressesa less constrained context to realize pre-emptive multitasking byhaving each active object residing in its own thread.• [Coplien and Schmidt, 1995] contains a pattern language calledPatterns of Events that deals with ’event processing in a distributedreal-time control and information system’.148COOPERATIVE MULTITASKINGAsynchronous ControllerIntentEncapsulate a finite state machine within an active object to efficientlycontrol a modest set of related asynchronous sub-tasks.AKANone knownProblemContextYou have a single overall task that you wish to perform that requires amodest set of asynchronous requests, or sub-tasks, to be performed in aprescribed order.Summary• You wish to keep your thread responsive to higher-priority eventsignals, such as those from end user events, whilst the overall task isbeing performed.• You wish to reduce your maintenance costs by encapsulating thecontrol of which sub-task is performed next within a single class sothat future changes only need to be done in this one place.• You wish to reduce the amount of RAM used by your software, forexample by reducing the number of threads running in your processand by minimizing the code size of your solution.• You wish to reduce your maintenance costs by reducing the time thatyou spend debugging your software.• You wish to reduce the time that you need to spend testing yoursoftware looking for defects.DescriptionIf you’ve read Active Objects (see page 133), then your first question isgoing to be: ’What’s the difference between a task and a sub-task?’.
Thisis a question of granularity but the essence is that a task is somethingthat other parts of your software are interested in completing whilst subtasks are the internal steps needed to achieve that end. Hence no otherprocess is interested in whether an individual sub-task completes unlessit affects the overall task. Another indication that you’re dealing with aset of related sub-tasks is when they operate on a similar set of resources.ASYNCHRONOUS CONTROLLER149For instance, a task might be to import a series of contacts from a fileinto the Contacts database. This can only be achieved by performing thefollowing sub-tasks:1.
Open the Contacts Database.2. Read the contacts from the file.3. Write the contacts into the Contacts Database.Each of these sub-tasks involves making an asynchronous request to aservice provider. For steps 1 and 3, the service provider is the ContactsDatabase whilst for step 2 it is the File Server. Using Active Objects(see page 133) is an obvious choice to encapsulate these requests sothat you get the benefits of cooperative multitasking that they provide.You may find it helpful here to make a distinction between external subtasks, which are asynchronous requests to an external service providerusually in another thread, and internal sub-tasks, which are sub-tasksperformed locally that are done asynchronously to avoid impacting theresponsiveness of your entire thread.Your next question is likely to be ’Why not have an active objectper sub-task?’. However, for this context that’s unlikely to give the bestsolution because by definition the sub-tasks are closely coupled16 andthere aren’t a large number of them, so separating the sub-tasks intodifferent classes is likely to reduce the maintainability of the solution inaddition to increasing your code size unnecessarily.17Another good question is ’How do I control the order in which thesub-tasks are performed?’ Clearly a single class, known as the controller,should take responsibility for this and use some kind of Finite StateMachine (FSM)18 to implement this.
This is because a controller is anevent handler and FSMs provide a very effective way of modeling eventhandling with each state starting when an asynchronous request is madeand ending when the request is completed.The most typical distinguishing feature of an FSM is that it processes a stream or sequence of input values, in which the value ofeach input must be interpreted in the context of previous input values.Simple examples include almost any application that responds to userinput; more complex examples include parsers and communicationsprotocols.A number of factors determine the complexity of a state machine,including the range of possible input types; the number of states (known16 Through their mutual use of the resources and the ordering imposed between them bythe overall task.17 Especially since CActive -derived classes use virtual functions, which requires a vtable.18en.wikipedia.org/wiki/State machine.150COOPERATIVE MULTITASKINGas sub-tasks here) that need to be performed; and the complexity of theactions which must be performed within each state.There are a number of well-known approaches to implementing a statemachine:• Function TablesActions are dispatched via function pointers or objects that arelooked up in a table based on the current state.
Typically, thesekinds of FSM are complex to understand, design and code. Whilstcode generators can help with this, they require additional toolingsupport. This solution is best reserved for components which areperformance-critical and for which this factor is more important thanthe maintenance costs. However, in this context we are generallywaiting for asynchronous requests to complete and hence the timetaken to make the request itself is going to be less significant than thetime simply waiting for the response. Hence this approach isn’t idealfor us.• State Pattern [Gamma et al., 1994]This classic approach brings all the benefits of an object-orienteddesign but requires a class to be used for each state.
This would meana different class for each sub-task which we’ve already discussed asnot being ideal for this context.• Switch Statements and an Enumerated StateThis has the advantage of being simple to understand and implementfor a modest number of sub-tasks however a C-style implementationwould be poorly encapsulated.ExampleSymbian OS includes an implementation of the relatively simple HTTPprotocol. This is accessed via the HTTP Transport Framework, notablyRHTTPSession and RHTTPTransaction, however, these simply provide a convenient interface to the actual protocol itself which ishosted in the Socket Server as part of the Communication Infrastructure.19The protocol is functionally decomposed into a number of tasks thatmirror those required by clients of HTTP.
These tasks include:• establishing an HTTP connection with a remote device• listening for incoming connections from remote devices19See [Campbell et al., 2007] for more details of this.ASYNCHRONOUS CONTROLLER151• reading in data from remote devices and passing it to a client• taking data from a client and passing it to a remote device.All of these tasks can be broken down into sub-tasks as shown inFigure 5.5.
For the purposes of this example, we’ll just focus on theconnection task which can be represented by the following state diagramwhich reflects the need to first find out the IP address of the remote deviceby doing a DNS lookup followed by the actual connection attempt:Eldle[KErrNotReady][KErrNotReady]EWaitingForDNSLookupEWaitingForConnectionConnectedInitialErrorFigure 5.5Example of the Asynchronous Controller pattern (connection state diagram)SolutionThe fundamental design is to encapsulate both the event handling andthe FSM into a single asynchronous controller, implemented as an activeobject.