Sunday, September 7, 2008

Asynchronous Method Execution Using Delegates

There are a number of scenarios in which asynchronous execution can be a valuable design technique. Delegates provide an easy, powerful abstraction for asynchronous method execution.

Let's assume that we have the method GetCustomerList which is a long running method and we want to execute it asynchronously.



class DataAccessCode{
public static string[] GetCustomerList(string state){
// call across network to DBMS or Web Services to retrieve data
// pass data back to caller using string array return value
}
}



This method can be executed asynchronously using delegates.

1. Define a delegate type matching the signature of the method you want to invoke asynchronously.

public delegate string[] LongRunning(string state);

2. Create a delegate object and bind it to the GetCustomerList method.

LongRunning method = new LongRunning(DataAccessCode.GetCustomerList);

3. Call BeginInvoke.

a. When you make a call to BeginInvoke on a delegate object, you are essentially asking the common language runtime (CLR) to begin executing the handler method asynchronously on a secondary thread. While executing a method asynchronously with a call to BeginInvoke is powerful, it's also fairly easy because you don't have to be concerned with creating and managing a secondary thread. The CLR does this for you automatically.

b. When you call the BeginInvoke method, the delegate object places a request in a special internal queue. The CLR maintains a pool of worker threads that are responsible for servicing the request in this queue. Asynchronous execution is achieved because the thread that calls BeginInvoke is not the same as the thread that executes the handler method.

c. The call to BeginInvoke returns right away and the calling thread can then move on to other business without having to wait for the CLR to execute the target method.

IAsyncResult result = method.BeginInvoke("Delhi",null, null);

4. Call EndInvoke

string[] customers = method.EndInvoke(result);

a. EndInvoke allows you to retrieve the return value from an asynchronous method call.

b. A call to EndInvoke requires you to pass the IAsyncResult object associated with a particular asynchronous call.

c. Parameter list for EndInvoke will include any ref parameters defined within the delegate type so you can also retrieve any output parameters.

d. If the asynchronous method experiences an unhandled exception, that exception object is tracked by the CLR and then thrown when you call EndInvoke.

e. Forgetting to call EndInvoke will prevent the CLR from cleaning up some of the resources required in dispatching asynchronous calls. Therefore, you should assume your application will leak if you make calls to BeginInvoke without also making associated calls to EndInvoke.

f. A call to EndInvoke returns right away if the worker thread has already completed the execution of the handler method. However, a call to EndInvoke will block if the asynchronous call hasn't yet started or is still in progress.

g. When to call EndInvoke

i. Immediately after calling BeginInvoke

ii. Wait on WaitHandle and then call EndInvoke

iii. Poll for IsCompleted to be true and then call EndInvoke when it is true.

iv. From callback

In a GUI application, all the code usually runs on a single thread known as the primary UI thread. The primary UI thread is important in a Windows Forms UI application because it is in charge of maintaining the responsiveness of the user interface. If you freeze the primary user interface thread with a long-running call across the network, the hosting application will be unresponsive to the user until the call returns. So, you should take advantage of a handy feature of delegates that lets you set up a callback method that's automatically executed by the CLR when an asynchronous method call is completed.

When the application calls BeginInvoke, the CLR executes the handler method using a worker thread from the CLR thread pool. Next, that same worker thread executes the callback method. When this work is complete, the CLR returns that worker thread to the pool so it is available to service another asynchronous request.

It's very important to understand that the callback method does not run on the primary UI thread that made the call to BeginInvoke. Once again, the callback method is executed on the same secondary thread that executed the asynchronous method.

There is an important threading rule to follow when programming with Windows Forms. The primary UI thread is the only thread that's allowed to touch a form object and all of its child controls. That means it's illegal for code running on a secondary thread to access the methods and properties of a form or a control. However, you must remember that a callback method executes on a secondary thread and not the primary UI thread. That means you should never attempt to update the user interface directly from this kind of callback method. To be more concise, the callback method that's running on the secondary thread must force the primary UI thread to execute the code that updates the user interface.






IAsyncResult

public interface IAsyncResult {
object AsyncState { get; }
WaitHandle AsyncWaitHandle { get; }
bool CompletedSynchronously { get; }
bool IsCompleted { get; }
}

a. The IAsyncResult object allows you to monitor an asynchronous call in progress.
b. The property IsComplete allows you to monitor the status of an asynchronous call and determine whether it has completed.

AsyncCallback

public delegate void AsyncCallback(IAsyncResult ar);

No comments: