IntentProvide a framework for an initiating a long-running operation and periodically checking the completion status. ProblemAlthough the Notifying Thread Manager provides a simple structure for calling an operation asynchronously, it assumes an event-driven application that does not have any sequential processing requirements. In servicing a Web request, either for a service or a page, the developer does not typically have the ability to trigger notifications back to the client to indicate completion of the operation except for the actual response to the request. Once the request is complete, the operation needs to be complete. Although it seems like extra overhead to make an operation an asynchronous fashion, there are situations were it definitely makes sense. Obviously, if there are multiple operations to be performed that are mutually independent, making the separate operations asynchronous will allow the process to take better advantage of multiple processors. This pattern will also help with situations where the user is periodically given an update of the status of the process. ForcesUse the Pollable Thread Manager pattern when:
StructureThis pattern also involves two classes, the client and the PollableThreadManager (Figure 3.5). In this pattern, the client simply holds a reference to the Thread Manager, rather than providing callbacks to the manager. The Manager provides three methods, two for initiating the execution and one to wait for the execution to complete. All three of the methods will return an AsyncReturn value, which is an enhanced Boolean value that can indicate the process is still running in addition to displaying a true or false indicator. Figure 3.5. Pollable Thread Manager pattern.
ConsequencesThe Pollable Thread Manager has the following benefits and liabilities:
Participants
ImplementationAlthough the Pollable Thread Manager provides more methods to the client, the implementation is actually somewhat easier than that of the Notifying Thread Manager because delegates are not necessary for notification. In our sample application, we use a separate form to wrap the calling of the Pollable Manager. This form creates and holds an instance of the manager as a member of the class in the form load method. The execution is also initiated at this time. Listing 3.5 Calling the Pollable Thread Manager.mExecute = new PollableThreadManager(mRow.ID, mRow.RequestValue); mExecute.BeginExecution(); The method we use to initiate the execution, BeginExecution, is very similar to the ExecuteAsync provided by the previous pattern because the thread is initiated in the same way. The only two differences are that there are no delegates to store and that we give the caller the option of waiting for a specific amount of time before the initial call returns. For simplicity, an override is provided for BeginExecution in our example to set the default wait to 1 second. Listing 3.6 Pollable Thread Manager BeginExecution method.public AsyncReturn BeginExecution(int MillisecondsToWait) { // By default, we don't have an error mbError = false; // Start the thread ThreadStart startThread = new ThreadStart(DoExecution); mExecution = new Thread(startThread); mExecution.Start(); // Give it a chance to complete before we go on return WaitForCompletion(MillisecondsToWait); } The return value of the above function allows us to return not only true or false but also the process still running value that makes this pattern possible. In a more complex implementation, this return might be expanded to include a percentage of completion in addition to a simple running value. In the event that a CompletedUnsuccessfully return code is received, the caller is expected to check the ExceptionThrown property to retrieve the actual exception that occurred. Listing 3.7 Pollable Thread Manager AsyncReturn enumeration.public enum AsyncReturn : int { /// We have no idea how we got here. Unknown = -2, /// The transaction is not complete, yet. Running = -1, /// The transaction has completed unsuccessfully CompletedUnsuccessfully = 0, /// The transaction has completed successfully CompletedSuccessfully = 1 } The WaitForCompletion method has the obvious potential to be called a large number of times and, although we are technically waiting anyway, we want to keep this function as small as is reasonably possible. This function will check to see whether the thread is still running and, if so, will wait for the specified time period for completion. The Join method provides our synchronization in this example by immediately returning true if the thread completes or returning false if it does not complete in the time specified. Once we know the thread is complete, it is just a matter of returning CompletedUnsuccessfully or CompletedSuccessfully, as indicated by the mbError member. Listing 3.8 Pollable Thread Manager WaitForCompletion method.public AsyncReturn WaitForCompletion(int MillisecondsToWait) { // If the thread is either stopped or not running // yet, wait for it to complete if (0 == (mExecution.ThreadState & (ThreadState.Stopped ThreadState.Unstarted))) { if (!mExecution.Join(MillisecondsToWait)) { // We didn't finish yet, let the caller know return AsyncReturn.Running; } } // We have an error in execution if (mbError) return AsyncReturn.CompletedUnsuccessfully; // Success!!! return AsyncReturn.CompletedSuccessfully; } Our sample uses a timer on the form to periodically initiate our call to the WaitForCompletion method. If the method returns either CompletedSuccessfully or CompletedUnsuccessfully, the new status is set and the form is closed. Again, DoExecution is our method that performs the actual work, and again, we are using a separate class to do the work, which results in a very simple function. The simple act of exiting the function terminates the thread, which allows WaitForCompletion to return Successful or Unsuccessful. In this pattern, we will store the result or the exception as a member of the class to be retrieved by the caller, once the above occurs. Listing 3.9 Pollable Thread Manager DoExecution method.protected void DoExecution() { try { // Get the answer mResult = Factorer.Factor(mToFactor); } catch(Exception ex) { // Store the exception mException = ex; // ...and the fact that we failed. mbError = true; } } If our calendar application example above used multiple Web services to retrieve the availability for the conference room, we might not want the user to be able to do anything to the calendar while the availability was being retrieved for the resource. The Pollable Thread Manager pattern can be used to wrap the multiple calls (Figure 3.6). Figure 3.6. Pollable Thread Manager example.
The application would begin the execution by calling BeginGetFreeBusyInfo and periodically updating the interface by using the WaitFor call to find out whether the call is complete. The WaitFor operation can return a completion percentage, which is then used to set a progress status in the interface. Related Patterns
|