Pollable Thread Manager


Intent

Provide a framework for an initiating a long-running operation and periodically checking the completion status.

Problem

Although 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.

Forces

Use the Pollable Thread Manager pattern when:

  • Working with a request, immediate-response situation

  • Operations require periodic update to users

  • Multiple operations could be processed at once

Structure

This 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.

graphics/03fig05.gif

Consequences

The Pollable Thread Manager has the following benefits and liabilities:

  1. It provides a simple calling methodology for asynchronous operations . As with the Notifying Thread Manager, the main object of this pattern is to simplify the calling structure for asynchronous operations. Although this seems somewhat contradictory, both patterns attempt to simplify the operation with different operational objectives.

  2. The client must periodically poll the thread manager . If we are dealing with a Windows form, this will probably need to be done through some type of timer. If this is being done for multiple operations for a Web request, all running threads will have to be periodically polled until all are complete.

  3. It will require tuning . Because this pattern will provide the ability to set delays and polling frequencies, some tuning will be involved to determine the compromise that provides the best level of performance and user feedback.

Participants

  • Client ” In this pattern, the client can be any class that can periodically check for completion of the process execution.

  • PollableThreadManager ” Any operation for which you want periodically to check the processing status, especially operations that have a distinct number of steps that can indicate the progress of the operation.

  • AsyncReturn ” An enumeration that provides processing status, as well as completion success. In our example, we provide simply a running value, a completed successful value, or an unsuccessful completion value, but additional values can easily be added to indicate either percentage of completion or specific progress values.

Challenge 3.1

When would you leverage notify versus polling in a threaded interface? Could you do both effectively?

The Multisync Thread Manager should provide you with a solution ( next ).

Implementation

Although 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.

graphics/03fig06.gif

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

  • NotifyingThreadManager (Eshelman)

  • MultiSyncThreadManager (Eshelman)

Challenge 3.2

How would you call a Web service using the interface threading patterns shown here, and what best practices would you employ ?

The answer is in Chapter 4. By combining both the threading patterns with that of Service Factories (both Chained and Unchained), you should able to come up with a sound design for doing just this.



.NET Patterns. Architecture, Design, and Process
.NET Patterns: Architecture, Design, and Process
ISBN: 0321130022
EAN: 2147483647
Year: 2003
Pages: 70

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net