Notifying Thread Manager


Notifying Thread Manager

Intent

Provide a framework for an interactive user interface to invoke a long-running operation in a more managed fashion.

Problem

Many times a user interface must initiate an operation that the developer would like to disconnect from the interface's operation. Although there are many reasons, the most common tend to be when multiple operations need to be performed at once or when an operation could potentially take longer than the user should be expected to wait for a response.

Some operations will always take time to complete. A user interface should allow a user to continue doing work while waiting for the operation, or multiple operations, to complete in background. As the use of Web services proliferate, the response times of some services will vary. The user of an interactive application (aka fat client) should not be left with just an hourglass while the operation completes.

Although the .NET framework goes a long way toward simplify initiating new threads to wrap an operation, the job of receiving the result of the operation still leaves some hurdles to be crossed. The developer of a Windows form can fairly easily fire off a thread to perform an operation and receive the result through an event but must still coordinate back with the form's thread to ensure nonoverlapping operations.

This pattern solves the problem by wrapping the logic necessary to get the operation's result, as well as any possible exceptions, back to the calling form's thread.

Forces

Use the Notifying Thread Manager pattern when:

  • Working with a fat client, Windows forms implementation

  • The operation is expected to take an extended or unknown amount of time

  • Multiple operations could be processed at once

Structure

The structure of the Notifying Thread Manager pattern consists of two classes, the Client and the NotifyingThreadManager itself (Figure 3.1). The Client can be any class that will be the consumer of the operation provided by the NotifyingThreadManager. It must implement two additional methods to serve as the callbacks from the thread manager. The NotifyingThreadManager class implements a single method that will start the asynchronous execution. This method accepts the delegates for the two methods the client implements, as well as a reference to the actual Windows control that we will use to synchronize any callbacks.

Figure 3.1. Notifying Thread Manager pattern.

graphics/03fig01.gif

Consequences

The Notifying Thread Manager has the following benefits and liabilities:

  1. It provides a simple calling methodology for asynchronous operations . Because we are dealing with an asynchronous call, it is unavoidable that we need some mechanism to receive the results, but this pattern eliminates the need for the caller to know the thread management details by automatically resyncing with the calling controls thread.

  2. It can notify only one form on completion . For simplicity of method calls and implementation, this pattern will support notifying only one target at a time. This is consistent with the intent of this pattern but should be noted.

  3. It does not address synchronization of data . As implemented, this pattern accepts all data it needs to work on and returns the complete result set. Operations requiring any additional information from the user interface while executing will need additional synchronization techniques.

Participants

  • Client ” This can be any object derived from the Windows.Form.Control. It will be used to synchronize responses from the asynchronous operation back to the user interface.

  • NotifyingThreadManager ” This can wrap any operation that you might want to call asynchronously. It could be a long-running application, such as a lengthy calculation. It could be an operation that you cannot determine at development time how long it might take to complete, such as calling a Web service. It might even be a very short operation that you just want to initiate a number of threads to process simultaneously .

Implementation

The Notifying Thread Manager relies on the .NET concept of delegates to know what to do when the execution completes. Those familiar with C (or C++) programming will find this concept very common to function pointers. Those familiar with the "gang of four" (GoF) reference (covered in more detail in Chapter 2) will find it very similar to their Delegate pattern.

The delegate keyword in C# ( Delegate in VB) allows you specify the signature of a single function type in a manner similar to how an interface allows you to specify the signature of an entire class. Once specified, you can create an instance of the delegate type to refer to a specific function and use the Invoke method of the delegate to call the original function. When calling Invoke, parameters are passed in just as though calling the original function.

The Notifying Thread Manager pattern will make use of two delegate types, one for successful completion and one for exception handling. The declarations for the delegates in this example are shown below. In our example, both delegate types take an ID and the value to be factored as the first two parameters. These values help the caller to tie the result back to the initial request when multiple requests are executing simultaneously.

Listing 3.1 Notifying Thread Manager delegate definitions.
 public delegate void CompleteEvent(int ID,    ulong Factored,    string Result); public delegate void ExceptionEvent(int ID,    ulong Factored,    Exception ExceptionThrown); 

The third parameter in both cases is the actual value we are going to act on. For a CompleteEvent, the result of the operation is passed to the caller. For an ExceptionEvent, the exception thrown is passed back to the caller.

The Notifying Thread Manager exposes only one method, ExecuteAsync, to the caller (besides the constructor). The entire purpose of this function is to store the information necessary to do the notifications and start execution of the wrapped thread. We will need to know the control that we will sync back with, as well as the two delegates to handle the result.

Listing 3.2 Notifying Thread Manager ExecuteAsync method.
 public void ExecuteAsync(System.Windows.Forms.Control ControlToNotify, CompleteEvent NotificationDelegate, ExceptionEvent ExceptionDelegate) {    // Store the information we will need to notify on completion    mControlToNotify = ControlToNotify;    mFactoringComplete = NotificationDelegate;    mFactoringErrored = ExceptionDelegate;    // Start the thread    ThreadStart startThread = new ThreadStart(DoExecution);    mExecution = new Thread(startThread);    mExecution.Start(); } 

The ThreadStart instance is another example of a delegate. From the .NET documentation, you can find that the signature for any function wrapped by this delegate must take no parameters and return void. Our function, DoExecution, meets these requirements and will perform the bulk of our processing. Once the delegate is created, we pass that to the new thread we create for asynchronous processing.

In our sample application, we will be using a very slow factoring operation to act as our long-running operation. We have three ways to initiate the operation, either synchronously, using the Notifying Thread Manager, or using the Pollable Thread Manager (described next ). The Notifying Thread Manager is available through any of the buttons to the right of the Notify label (Figure 3.2).

Figure 3.2. Sample threading application.

graphics/03fig02.jpg

When a Notify request is added, a new DataRow is added to an internal DataTable. This row is given a unique ID, and the start time is stored so we can calculate the execution time when complete.

The application then creates a new Notifying Thread Manager with the ID of the row added and the value to be factored. The ExecuteAsync method of the new object is called, passing the form object and delegates for both the completion and the exception cases, as shown below.

Listing 3.3 Calling the Notifying Thread Manager.
 NotifyingThreadManager f = new NotifyingThreadManager(rr.ID, rr.RequestValue);    f.ExecuteAsync(this,    new NotifyingThreadManager.CompleteEvent(FactoringComplete),    new NotifyingThreadManager.ExceptionEvent(FactoringErrored)); 

As mentioned above, the DoExecution method does the actual work of this pattern. In our example, we use a separate class to encapsulate the actual operation to simplify readability of the example, but the actual implementation could just as easily have been included inline. Along the same lines, this function could also be a simple wrapper for a call to an external Web service.

In our example, the Notifying Thread Manager as seen in Figure 3.3 will call a static method on a Factorer class to retrieve a string containing all of the factors of an integer. This factoring algorithm was intentionally created to be as slow as possible.

Figure 3.3. Notifying Thread Manager pattern.

graphics/03fig03.gif

As mentioned above, delegates typically call the delegate function by using the delegate's Invoke method. The only problem with this approach is that the call executes on the thread of the caller of the method. If we have multiple threads completing at the same time, we need to provide some method of synchronization to avoid overlapping calls.

The BeginInvoke method of the System.Windows.Forms.Control class provides the method that we will use to synchronize calls. This method executes calls on the thread that the control's underlying handle has created, taking care of the synchronization issues for us.

The drawback of this method is that the BeginInvoke method takes a parameter of the generic type delegate, which forces us to use an array of generic object values to pass values, rather than the type-safe Invoke method. This shifts type checking to runtime, rather than compile time, but the potential for problems is minimized because we are starting from a defined delegate type.

The result is a function in which the actual processing boils down to only one line. The majority of this method is in building the parameter arrays for either the success or failure of execution and initiating those methods.

Listing 3.4 Notifying Thread Manager DoExecution method.
 protected void DoExecution() {    object[] paramArray = new object[3];    try    {       // Get the answer       string strFactor = Factorer.Factor(mToFactor);       // Build the parameter array for the completion event       paramArray[0] = mID;       paramArray[1] = mToFactor;       paramArray[2] = strFactor;       // Invoke the "Success" delegate       mControlToNotify.BeginInvoke(mFactoringComplete,          paramArray);    }    catch(Exception ex)    {       // Save the exception that occurred       mException = ex;       // Build the parameter array for the error event       paramArray[0] = mID;       paramArray[1] = mToFactor;       paramArray[2] = ex;       // Invoke the "Failure" delegate       mControlToNotify.BeginInvoke(mFactoringErrored,          paramArray);    } } 

This pattern is very useful for any operation of which an application would want multiple simultaneous requests. Consider, for example, a calendar application that provides a scheduling interface (Figure 3.4). The application can retrieve all the meetings a conference room is scheduled for in one call but has to make a separate call to retrieve the details for each individual meeting.

Figure 3.4. Notifying Thread Manager example.

graphics/03fig04.gif

The application can make use of the Notifying Thread Manage pattern to retrieve asynchronously the details for each of the meetings while the user looks at the current availability for the conference room. As the results from the individual calls come back, the Manager notifies the interface of the completion, and the user interface can update the details on the screen.

Related Patterns

  • PollableThreadManager (Eshelman)

  • MultiSyncThreadManager (Eshelman)

  • Proxy (GoF)



.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