Basic Instincts

Asynchronous Method Execution Using Delegates

Ted Pattison

Contents

Invoking a Method Asynchronously
IAsyncResult
Calling EndInvoke
Conclusion

This month I'll discuss the use of delegates to execute a method in an asynchronous fashion. I'll assume that you already know the fundamentals of programming with delegates, but if not, you should read the December 2002 and January 2003 Basic Instincts columns.

There are a number of scenarios in which asynchronous execution can be a valuable design technique. For example, you should use asynchronous execution in a Windows® Forms application when you want to execute a long-running command without blocking the responsiveness of the user interface. As you will see, programming with delegates makes it relatively easy to run a command asynchronously on a secondary thread. That means you can build a Windows Forms application that executes long-running calls across the network without freezing the user interface.

Executing methods asynchronously can also serve as a useful technique for a server-side application. Imagine you are writing the code behind an ASP.NET page to service a client request. What should you do if your code needs to execute two or more time-consuming calls across a network? For example, imagine you are required to write an ASP.NET page that must run a query against a remote database and also must execute a SOAP method on a remote Web Service to complete its work. If you don't use asynchronous method execution, you can only make one call across the network at a time. This means you can't start the second network call until the first network call returns. Asynchronous method execution using delegates lets you execute two or more network calls at the same time. This can significantly reduce the time it takes to process a server-side ASP.NET request, increasing the responsiveness of your application from the user's perspective.

Invoking a Method Asynchronously

Since you already know the fundamentals of programming delegates, I am going to present an example using a delegate object to execute a method in a synchronous fashion. Suppose you have written a shared method named GetCustomerList that executes a time-consuming call across the network to complete its work:

Class DataAccessCode
  Shared Function GetCustomerList(State As String) As String()
    '*** call across network to DBMS or Web Services to retrieve data
    '*** pass data back to caller using string array return value
  End Function
End Class

In this example the GetCustomerList method is defined with a single string parameter that accepts the name of a U.S. state. The method's return value is defined in terms of a string array that is used to pass a list of customers back to the method's caller.

To execute the GetCustomerList method using a delegate object, you must first define a delegate type with a matching signature. For example, you can define a delegate type named GetCustomerListHandler that takes a string parameter and returns a string array, like this:

'*** a delegate for executing handler methods
Delegate Function GetCustomerListHandler(State As String) As String()

Now that you have defined a delegate type with the correct calling signature, you can create a delegate object and bind it to the GetCustomerList method. Once you have created and bound a delegate object, you can execute GetCustomerList synchronously by calling the delegate object's Invoke method:

'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList

'*** execute method synchronously
Dim retval As String()
retval = handler1.Invoke("CA")

When you call Invoke on the delegate object, it forwards the call by executing the handler method for you. It's important to point out that a call to Invoke is synchronous. In other words, it's a blocking call because it doesn't return until the delegate object has finished executing GetCustomerList. When GetCustomerList completes its execution and returns, the delegate object takes its return value and passes it back to the caller of the Invoke method.

When the Visual Basic® .NET compiler generates a delegate type definition, it adds the Invoke method to support synchronous execution. The compiler also adds two other methods—BeginInvoke and EndInvoke—to support asynchronous execution.

You can rewrite the previous example using a delegate object to execute the GetCustomerList method in an asynchronous fashion. Instead of calling Invoke on the delegate object, call BeginInvoke, which requires you to pass any input parameters that are defined in the calling signature of the delegate. In my example, there is a single string parameter for passing a state name.

'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList

'*** execute method asynchronously
handler1.BeginInvoke("CA", Nothing, Nothing)

BeginInvoke also takes two other parameters that are not part of the calling signature of Invoke. I'll explain these two parameters later in this column.

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.

Figure 1 shows how the CLR deals with a call to BeginInvoke. 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.

Figure 1 Asynchronous Method Execution

Figure 1** Asynchronous Method Execution **

The CLR dynamically determines how many worker threads to add to the pool by examining a number of factors. In a typical desktop application, the CLR limits the pool to a handful of threads. In a busy server-side application where codebehind ASP.NET pages are making lots of concurrent calls to BeginInvoke, the CLR will grow the pool size up to a maximum of 25 threads for each processor on the host machine. That means the number of worker threads in the pool grows proportionally with the number of processors. For example, the maximum pool size on a host server computer with four processors is 100 threads.

Now consider the differences between a call to Invoke and a call to BeginInvoke. Imagine you have a month's worth of dirty laundry and you would like to have it cleaned. You have the choice of doing it yourself at the laundromat or dropping it off at the cleaners and having someone else do the work for you.

Going to the laundromat and doing the laundry yourself is like calling Invoke; it is a synchronous undertaking. You must stay at the laundromat until the work is completed, and when you leave you take the completed work (the clean laundry) with you.

Dropping your dirty clothes off at the laundry is like a call to BeginInvoke; it is an asynchronous undertaking. After you drop off your laundry, you can immediately move on to other business. However, you don't leave with clean clothes. You leave with a laundry ticket and you must return at some later time to pick up your clean clothes. Furthermore, you must keep track of your ticket because the guy behind the counter might refuse to give your clothes back if you lose it.

When you call BeginInvoke, you are essentially dropping off a request for a method execution at the CLR laundry. 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. After calling BeginInvoke you know that the CLR plans to execute that target method on the secondary thread, but you don't know exactly when it's going to happen. At a later point in time, you can ask the CLR whether it has finished executing that method asynchronously. You can also retrieve the method's return value. However, monitoring the status of an asynchronous call or harvesting an asynchronous method's return value requires you to provide the CLR with information about which asynchronous request you are investigating.

IAsyncResult

A call to BeginInvoke returns something that is analogous to a laundry ticket. More specifically, the BeginInvoke method has been designed to return an object that implements an interface named IAsyncResult. Here's an example of calling BeginInvoke and capturing the IAsyncResult object (the laundry ticket) when dispatching an asynchronous method call:

'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList

'*** execute method asynchronously and capture IAsyncResult object
Dim ar As System.IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)

The IAsyncResult object allows you to monitor an asynchronous call in progress. It also allows you to retrieve the return value and any output parameters whenever an asynchronous method has finished executing.

It is important for you to understand that there isn't necessarily a one-to-one relationship between a delegate object and an IAsyncResult object. For example, one delegate object can be used to start two or more asynchronous calls that will execute at the same time, as shown here:

'*** create delegate object and bind to target method
Dim handler1 As GetCustomerListHandler
handler1 = AddressOf DataAccessCode.GetCustomerList

'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)

In this example, two different asynchronous calls are started at once. Each of these asynchronous calls is going to be run on a separate thread from the worker thread pool, as shown in Figure 2. This can be valuable because you can make two or more calls across the network in parallel. However, it's important for you to remember that each one of these asynchronous calls has its own associated IAsyncResult object.

Figure 2 Two Asynchronous Calls

Figure 2** Two Asynchronous Calls **

The IAsyncResult interface exposes a property named IsComplete that allows you to monitor the status of an asynchronous call and determine whether it has completed. While this type of polling technique isn't efficient in most designs, it can be useful in a handful of situations. The following is an example of using the IsComplete property to determine whether the CLR has finished executing an asynchronous call:

Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)

'*** allow some time to pass

'*** check to see if the async call has completed
If (ar.IsCompleted) Then
  '*** retrieve method return value
End If

Calling EndInvoke

You retrieve the return values and any output parameters by calling the EndInvoke method on the delegate. EndInvoke is defined with the same type of return value as the calling signature of the delegate type itself:

Dim ar As IAsyncResult
ar = handler1.BeginInvoke("CA", Nothing, Nothing)

'*** allow some time to pass

Dim retval As String()
retval = handler1.EndInvoke(ar)

As you can see, calling EndInvoke allows you to retrieve the return value from an asynchronous method call. Also note that a call to EndInvoke requires you to pass the IAsyncResult object associated with a particular asynchronous call. This is just like giving the cleaners your ticket when you return to pick up your clean clothes. The IAsyncResult object allows the CLR to determine which asynchronous call you are interested in.

Also note that the parameter list for EndInvoke will include any ByRef parameters defined within the delegate type so you can also retrieve any output parameters. However, the example I used here doesn't involve any output parameters, so the call to EndInvoke only requires that you pass a single parameter holding a reference to the IAsyncResult object.

You have seen that a call to EndInvoke is important because it allows you to retrieve the return value and any output parameters. Calling EndInvoke is also vital because it allows you to see if the asynchronous method executed successfully. If the asynchronous method experiences an unhandled exception, that exception object is tracked by the CLR and then thrown when you call EndInvoke, as shown in the following code:

Dim retval As String()
Try
  retval = handler1.EndInvoke(ar)
Catch ex As Exception
  '*** deal with exception here
End Try

It's important to complement every call to BeginInvoke with a call to EndInvoke. If you never call EndInvoke, you can never really be sure whether an asynchronous call executed successfully. Forgetting to call EndInvoke can allow runtime exceptions to go unnoticed, which can introduce bugs to your code that are hard to track down and fix.

You have already seen a few good reasons why a call to BeginInvoke requires a call to EndInvoke. There is one more very important reason why you should always call EndInvoke—it's the law. 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.

Now let's focus our attention on how the timing works when you call EndInvoke. 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. You must consider the implications of this fact when you design your applications.

Think about that situation in which you were writing the code behind an ASP.NET page and you wanted to execute two calls across the network at the same time. The fact that EndInvoke is a blocking call makes it pretty easy to coordinate multiple paths of execution. The code in Figure 3 simultaneously executes two methods asynchronously in order to make two network calls in parallel. However, it's essential that both network calls complete successfully before the request as a whole can finish. The first call to EndInvoke blocks until the first asynchronous method has completed. The second call to the EndInvoke method blocks until the second call has completed.

Figure 3 Executing Multiple Methods Asynchronously

'*** server-side request in code behind an ASP.NET page

'*** execute multiple methods asynchronously
Dim ar1, ar2 As System.IAsyncResult
ar1 = handler1.BeginInvoke("CA", Nothing, Nothing)
ar2 = handler1.BeginInvoke("WA", Nothing, Nothing)

'*** capture all return values
Dim retval1, retval2 As String()
retval1 = handler1.EndInvoke(ar1)
retval2 = handler1.EndInvoke(ar2)

'*** finish processing request

What happens if the second asynchronous call finishes before the first asynchronous call? It really doesn't matter. In this design it is irrelevant which of the two network calls finishes first. Both calls to GetCustomerList have to complete before the server-side request can finish its work. Therefore, the blocking behavior of EndInvoke plays an important role. It takes multiple paths of execution and joins them to create one logical path of execution.

All the while, the CLR is doing all the complicated work behind the scenes. It is managing threads, dispatching asynchronous calls, and blocking certain threads until other threads have completed their work. All you have to do is call BeginInvoke and then call EndInvoke. You can clearly see that delegates provide an easy, powerful abstraction for asynchronous method execution.

Now let's turn our attention to asynchronous method execution in a Windows Forms application running on a desktop application. This is a much different scenario than a server-side application because here 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.

In this case it's critical to use asynchronous execution so you don't lock down the user interface. You've seen that it's fairly easy to dispatch an asynchronous call using the BeginInvoke method, but a problem arises. How does your application respond when an asynchronous method call has finished executing? How do you update the user interface when an asynchronous call is finished? You can use a polling technique and continually test for completion, but that isn't very efficient. Instead, 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.

The code in Figure 4 shows an example of a Windows Forms application that executes the GetCustomerList method asynchronously. All the code has been structured within a class named Form1. Within this class definition, you can see one possible approach for setting up a callback method when dispatching an asynchronous method call using BeginInvoke.

Figure 4 Callback Method

Public Class Form1 : Inherits System.Windows.Forms.Form

  Private TargetHandler As GetCustomerListHandler _
                           = AddressOf DataAccessCode.GetCustomerList

  Private CallbackHandler As AsyncCallback _
                             = AddressOf MyCallbackMethod

  Sub cmdExecuteTask_Click(sender As Object, e As EventArgs) _
      Handles cmdExecuteTask.Click
    '*** execute method asynchronously with callback
    TargetHandler.BeginInvoke("CA", CallbackHandler, Nothing)
  End Sub

  '*** callback method runs on worker thread and not the UI thread
  Sub MyCallbackMethod(ByVal ar As IAsyncResult)
    '*** this code fires at completion of each asynchronous method call
    Dim retval As String()
    retval = TargetHandler.EndInvoke(ar)
  End Sub
End Class

Let's walk through the code. When you want to set up a callback method, you must work with a delegate type named AsyncCallback that is defined by the Framework Class Library in the System namespace. When you want to create a callback method, you must define this method with a calling signature that conforms to the calling signature of the AsyncCallback delegate type. MyCallbackMethod in Figure 4 is an example of a method defined with the proper calling signature. As you can see when looking at the code, the callback method must be defined as a Sub procedure with a single parameter of type IAsyncResult.

You should observe that the Form1 class contains two fields, each of which holds a reference to a delegate object. The first field, TargetHandler, holds a reference to a delegate object that's used to dispatch the asynchronous call to the GetCustomerList method. The second field, CallbackHandler, holds a reference to a delegate object that's bound to MyCallbackMethod. These delegate objects are both used in the call to BeginInvoke.

Now examine the call to BeginInvoke inside the event handler method cmdExecuteTask. The TargetHandler field is used to execute BeginInvoke and dispatch the asynchronous call. The second parameter passed to BeginInvoke is the reference to the delegate object that's bound to MyCallbackMethod. In essence, you pass a reference to a delegate to point the CLR to whichever callback method you'd like to use. This is all that's required to set up a callback method after an asynchronous method call.

Now let's see how things work in my example when an asynchronous call is dispatched. 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.

The last parameter passed in a call to BeginInvoke is the AsyncState parameter. AsyncState is a very open-ended parameter that you can use to pass anything you want. It is defined in terms of the Object class so it can be used to pass any value or object. In the example I presented in Figure 4, there was no need to utilize the AsyncState parameter, so a value of Nothing was passed.

In other designs, it can be convenient to use the AsyncState parameter to pass a value or an object from the code that calls BeginInvoke to the implementation of the callback method. For example, imagine you need to pass an Integer so that it's available in the implementation of the callback method. When you call BeginInvoke, you can pass the Integer value in the AsyncState parameter like this:

Dim MyValue As Integer = 100
TargetHandler.BeginInvoke("CA", CallbackHandler, MyValue)

The value or object that is passed in the AsyncState object is tracked by the IAsyncResult object and can be retrieved using a property named AsyncState. Here's an example of retrieving the Integer value in a callback method:

Sub MyCallbackMethod(ByVal ar As IAsyncResult)
  Dim y As Integer = CInt(ar.AsyncState)
  '*** other code omitted for brevity
End Sub

Remember that the AsyncState property is defined in terms of the Object class, so you typically need to convert it explicitly to a more specific type before you can program against the value or object that it contains.

You've seen that it's relatively easy to call BeginInvoke from the code behind a Windows Form to dispatch an asynchronous method call. You have also learned how to set up a callback method that fires automatically when the asynchronous method call has completed. The last thing you need to learn is how to update the UI to tell the user that the work of the asynchronous call has been finished. This can be tricky. It's hard to write the code correctly and you can get into serious trouble if you don't do it right.

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 such as MyCallbackMethod 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. The code to do this is fairly involved and I am going to wait until next month's column to explain exactly how to do it. For now just understand that it's not an acceptable programming technique to update the user interface directly from a callback method such as MyCallbackMethod due to the threading constraints imposed by the framework.

Conclusion

When executing an asynchronous method call using a delegate, each delegate type provides a BeginInvoke method to start a method call asynchronously on a secondary thread from a thread pool maintained by the CLR. Each delegate type provides the complementary EndInvoke method that can be used to harvest an asynchronous method return value and to determine whether the method has executed without any unhandled exceptions.

One of the most valuable aspects of asynchronous execution using delegates is that you don't have to worry about creating and managing secondary threads. The CLR automatically maintains a pool of worker threads for you, which means you can benefit from techniques involving asynchronous and parallel method execution without ever having to program directly against threads. All that remains to be learned is how to call BeginInvoke and EndInvoke at the right times.

Send your questions and comments for Ted to  instinct@microsoft.com.

Ted Pattison is a cofounder of Barracuda.NET (https://barracuda.net), an education company that assists companies building networked applications using Microsoft technologies. Ted is the author of several books including Building Applications and Components with Visual Basic .NET (Addison-Wesley, October 2003).