MSDN Magazine > Issues and Downloads > 2001 > August >  .NET Delegates: Making Asynchronous Method Call...
From the August 2001 issue of MSDN Magazine
MSDN Magazine

.NET Delegates: Making Asynchronous Method Calls in the .NET Environment

Richard Grimes
This article assumes you're familiar with C#
Level of Difficulty     1   2   3 
SUMMARY One of the many great features of the .NET Framework is that it has asynchronous infrastructure built in. In .NET you can call any method asynchronously by defining a delegate for the method and calling the delegate's asynchronous methods. This is beneficial to your application because when a synchronous call is made, the calling thread is blocked until the method completes whereas an asynchronous call is made on a different thread, and this allows the original thread to continue its work while the asynchronous call is in progress.This article explains delegates in .NET and how to use them to perform asynchronous calls, eliminating age-old threading problems.
When developing for Win32®, most programmers are used to accessing APIs synchronously: a thread initiates some task, then waits patiently for the task to complete. If the code reaches a higher level of sophistication, it could create a worker thread to make this synchronous call, freeing the main thread to continue with its work. Using worker threads to perform lengthy blocking calls is crucial for GUI applications because blocking the thread that pumps the message queue disables the UI of the application.
      Creating separate worker threads like this is never straightforward. Creating a new worker thread every time you need to make a blocking call could ultimately involve lots of threads, increasing resource consumption. In addition, if you're thinking about passing parameters to the worker thread and receiving results from it, you need to consider thread synchronization. You also have to think about how the caller thread will be notified when the worker thread has completed. These issues are not trivial.
      Microsoft® .NET asynchronous calls provide an infrastructure to address many of these issues. In this article I will describe how to use asynchronous calls and point out some of the issues you'll face when using them.

Asynchronicity

      In a synchronous call to a method, the caller thread blocks while the call is active. When the call completes, the method can return results through the return value of the method or through any out parameters of the method. Because the call is synchronous, the caller code is guaranteed that the call has completed and so the caller code is assured that the values returned are valid (as long as the method is bug-free).
      When a thread makes an asynchronous call to a method, the call returns immediately. The caller thread is not blocked; it is free to perform some other task. The infrastructure obtains a thread for the method invocation and delivers the in parameters passed by the calling code. This async thread can then run the method in parallel to the calling thread. If the method generates some data and returns this value, the calling thread must be able to access this data. The .NET asynchronous infrastructure supports two mechanisms: the calling thread can either ask for the results, or the infrastructure can deliver the results to the calling thread when the results are ready.
      COM on 32-bit systems can simulate asynchronous calls: a multithreaded server can create a new thread to handle a request and return immediately. To get results back to the calling code, either there must be an extra (blocking) method on the called interface that returns this data or the client code must implement a sink object that is a callback to which the results are delivered. This is illustrated in Figure 1.

Figure 1 Asynchronous Calls using COM
Figure 1 Asynchronous Calls using COM

      The problem with the first mechanism is that another call must be made to the object to obtain the results, and if the object is remote this has to be a network call. The problem with the callback mechanism is that it requires the server object to hold on to a reference to the sink object until the call has completed, which can cause a circular reference. In general, implementing asynchronous calls like this in COM results in multithreaded programming, with the attendant complications you always encounter with thread synchronization and marshaling.
      Windows® 2000 provides two other ways to make asynchronous calls: COM+ (or loosely coupled) events and COM asynchronous interfaces. For details on COM+ events see "The COM+ Event Service Eases the Pain of Publishing and Subscribing to Data" in the September 1999 issue of MSJ, and for COM asynchronous interfaces see "Marshaling Your Data: Efficient Data Transfer Techniques Using COM and Windows 2000," which can be found in the September 2000 issue of MSDN® Magazine.
      COM+ events allowed for the fire-and-forget style of programming, where the server makes the callback and the COM+ event system determines which code wants the event and, if necessary, runs the code before delivering the event. COM+ events are one of the COM+ component services, and since COM+ component services are .NET component services you can still use this style of programming in .NET. However, I won't go into the details here.
      COM asynchronous interfaces used a marshaling technique to allow you to both call an interface asynchronously and implement the method in an asynchronous way. The significant part is the glue between the client code and the object—the marshaling layer—which meant that the client could call the object synchronously or asynchronously regardless of the implementation of the interface. This also meant that the interface could be implemented to allow synchronous or asynchronous calls regardless of how the client calls it. The only provisos were that the objects had to run on Windows 2000 (but not under COM+) and that the interfaces were marshaled with proxy-stub DLLs.

Delegates

      Before I can talk about asynchronous calls, I need to introduce delegates. A delegate is essentially a safe function pointer. A C pointer to a function that takes a string and returns a size_t can be typedef-defined as follows:
typedef size_t (*FUNC)(const char*);
This means that I can create a pointer and assign the address of a function:
void printSize(const char* str)
{
   FUNC f = strlen; 
   printf("%s is %ld chars\n", str, f(str));
}
      The problem with C function pointers is that you can make them point to anything:
void throwException(const char* str)
{
   FUNC f = (FUNC)strcat;
   f(str);   // bye, bye
}
      Without the cast, the compiler will check that the type of the function pointer and the function that is being assigned to it are the same. However, casts are useful, especially when the function pointer is being returned by a general-purpose API function like GetProcAddress. There is no check at runtime that the function being called is of the right type.
      One of the goals of .NET is to be type-safe, and to disallow such simple but common errors. All types in .NET are self-describing, thus delegates and the methods they call are self-describing. Therefore, a delegate can be thought of as a self-describing function pointer. A delegate that represents a method that takes a string and returns an integer looks like this in C#:
public delegate int StringDelegate(string str);
This can be declared in a class or at global scope. The C# compiler will generate a new class from this declaration and derive it from System.MulticastDelegate. Figure 2 shows the methods of this class and its base class, System.Delegate. This means that I can call the methods of MulticastDelegate on any delegate:
public void PassMeADelegate(StringDelegate d)
{
   Console.WriteLine("I was passed a delegate to a {0} method",
      d.Method.ToString());
}
      Clearly, this also means that you can pass references to Delegate because implicit conversions to base class references are allowed. However, when you cast to a base class, the metadata of the delegate object persists.
public void CastAway(Delegate d)
{
   StringDelegate del = (StringDelegate)d;
}
In this code, the cast will compile, but will only succeed at runtime if the delegate, d, is actually of the type StringDelegate; if not, an exception of System.InvalidCastException will be thrown.
      .NET delegate classes behave as containers. The Combine and Remove methods are used to add and remove information about methods from this container, and GetInvocationList returns an array of delegates in this container. As the names suggest, Delegate holds the information about a single method, while MulticastDelegate holds the information about more than one method. You don't add the method information per se. Instead, you add two already initialized delegates together, through the static Combine method, creating a new delegate object that has information contained in the other two.

Using Delegates

      In Figure 3 I have converted the output from the Microsoft intermediate language disassembler, ILDASM, to C# for the delegate I showed previously. The class is sealed and ILDASM shows that these methods are empty—this is because .NET will provide the implementation at runtime. In fact, the C# compiler will only allow you to generate delegate classes using the delegate keyword. If you attempt to derive a class from System.MulticastDelegate, the compiler will generate the error CS0644, stating that it cannot inherit from special class System.MulticastDelegate.
      To use this delegate, you need a method that has the same signature—the same number and types of parameters and the same return type. The name does not have to be the same, the method can be a static or an instance method, and it can even be a private method. Delegate objects have to be initialized in their constructor with the method that will be called:
class MyCRT
{
   public int strlen(string s)
   {return s.Length;}
}

MyCRT a = new MyCRT();
StringDelegate d = new StringDelegate (a.strlen);
Here I have created a new delegate and initialized it with the MyCRT.strlen method on the instance of class MyCRT. Since I don't use the parentheses when I mention this method, the C# compiler knows that I am referring to a method pointer, and not trying to call the method. If MyCRT.strlen was static, then I could initialize a delegate without an instance of the class; I'd just pass the name of the method qualified with the class name.
      Calling the method is straightforward; all you need to do is call Invoke on the delegate object. However, C# makes this simpler by allowing you to call the delegate as if it were a method:
int i;
string str = ".NET delegates";
// same as i = d.Invoke(str);
i = d(str);
      Only the parameters and return value have to match when you pass a method to a delegate, so as you can see in Figure 4, the delegate actually refers to a private method.
      When a multicast delegate is invoked, it will make a call to all of the objects that have registered a method through a delegate. To enable this, all these delegates have to be combined together so that a single call to Invoke will invoke them all. C# provides the += and -= operators on the delegate class as a convenience: all these operators do is call the Combine and Remove methods with the appropriate parameters, as shown in the following code:
StringDelegate d;

public void AddADelegate(StringDelegate dd)
{
   // same as d = (StringDelegate)Delegate.Combine(d, dd);
   d += dd;  
}
      Only delegates of the same type may be combined; if you attempt to combine delegates of different types, you'll get a System.ArgumentException.
      In the previous code, the StringDelegate is a delegate that returns a value. If several delegates are combined, what value is returned? The answer is the return value of the last delegate to be called—and hence the last delegate to be combined. All other return values will be discarded. If you want to use a multicast delegate, then it makes little sense for the delegate to have a return value. If your delegates do not have return values, then it facilitates the fire-and-forget style of programming, where the delegate call is made merely to inform connected clients that something has happened. This is the style that Windows Forms uses to pass Windows messages to your code; the EventHandler delegate does not have a return value.
      If you have a multicast delegate and one of the methods called throws an exception, then this will stop the complete invocation, and no other delegates in the combined delegate will be called. Clearly, you should be careful not to allow exceptions to be thrown out of a method invoked through a delegate.

Delegate Callbacks

      Let's see how to use delegates to make callbacks, and look at one way to make asynchronous calls. The first requirement is to allow the server component to spin off the work request to another thread so that the method call can return immediately. .NET threads are based on the System.Threading.Thread class; the constructor takes a delegate which will be called as the thread procedure, as you can see in Figure 5.
      This isn't the whole story, of course. How do you pass parameters to the thread? How do you receive results from the thread? What happens if you have many calls to StartBigCalc—should you create a new thread each time? To pass parameters to the thread in a thread-safe manner you can use a separate object to wrap the method and the parameters for the work item, as you can see in the code in Figure 6.
      To get results back, you have a variety of options. First, either the calling object or the CThreadClass2 could have an object to hold the result; a reference to this object could be passed to CWorker. CWorker could write the results to this object, but it needs some signaling mechanism to indicate that the results are available. Or you could have the worker thread inform some client-provided code via a delegate, as shown in Figure 7.
      Here, I have declared a delegate that is designed to return a result. The worker thread constructor has a delegate parameter, which ultimately refers to a method in the client code. The worker thread code calculates a value using the input values and returns the result by calling the delegate. The client code could look like the code in Figure 8.
      The CCalling object is created on a different thread than the CWorker2 object that will do the callback through the delegate. In this code, only the worker thread will have write access to m_i through GiveMeAResult and only one thread will have read access to the m_i variable in DoCalc. I will ignore any possible thread synchronization issues because there is a more pressing problem: how does DoCalc know when m_i has been set? As a hack, I have used the Sleep method to allow the worker thread to do its work before I access m_i, but this is unsatisfactory. .NET has a better solution, as you'll see later.
      If the CDoAsync3 class is likely to accept many requests in a short period of time and the thread procedure is lengthy, it is not a good idea to create a new thread for every request. Thread context changes are expensive, and a large number of threads will involve lots of context changes. The solution here is to use a thread pool. Every process will have at most one thread pool, which is created for you by .NET. All you need to do is call the static ThreadPool.QueueUserWorkItem method and pass a WaitCallback delegate. When a thread becomes available in the thread pool, WaitCallback is invoked. For example:
public class CDoAsync4
{
   public void StartBigCalc(int i, InformMe d)
   {
      CWorker2 w = new CWorker2(i, d);
      ThreadPool.QueueUserWorkItem(
         new WaitCallback(w.DoBigCalc));
      t.Start();
   }
}
      The WaitCallback delegate has an object parameter which can be used to pass data to the method via an overloaded version of QueueUserWorkItem. This is especially useful if the method invoked by the delegate is static.
      From this review of threads and delegates you can see that lots of things can be done with them, but creating code that works safely in an asynchronous way is still a problem. Let's look at an alternative way of doing this.

Asynchronous Calls on Delegates

      Let's go back to the class you saw in Figure 3 that was generated by the C# compiler upon encountering the delegate keyword. The two important methods are shown here:
public virtual IAsyncResult BeginInvoke(string str, 
   AsyncCallback asc, object stateobject);
public virtual int EndInvoke(IAsyncResult result);
The BeginInvoke parameters will start with the input parameters of the delegate and EndInvoke parameters will start with the output parameters. In C#, parameters marked as out are output parameters, those marked with ref are both input and output parameters, and those parameters not designated as out or ref are input parameters.
      BeginInvoke starts the asynchronous call to the method, and then the infrastructure queues the method to run on a thread pool thread and creates synchronization objects needed to determine if the method has completed. If BeginInvoke throws an exception, then you'll know that the asynchronous method has not been called. EndInvoke is used to harvest the results and allow the system to perform cleanup. If the delegate method throws an exception, then the async thread is terminated and the exception is thrown again in the calling thread when your code calls EndInvoke. (There is an exception to this, which I'll discuss later.)
      The return value of BeginInvoke is the IAsyncResult interface on an AsyncResult object (see Figure 9). This object is returned from the BeginInvoke method before the delegate method has actually been called by the async infrastructure. The reason is that this object does more than give access to the result; it actually gives access to the remoting infrastructure that is used to make the asynchronous call.
      .NET remoting is componentized into message sink objects that handle message objects. Method calls made on proxy objects are represented as messages that are passed through the remoting layer to the actual object. Message sink objects are used to transmit these messages, and they are particularly important for handling the various context changes that may be required. There may be many different message sink objects involved in a call, and this is the reason for the AsyncResult.NextSink property: to give access to the next sink in the chain.
      When a synchronous call is made via a proxy, the method request is passed as the parameter to SyncProcessMessage, and the message sink performs its processing on the message before passing it onto the next sink in the chain. The final sink in the chain calls the actual object method, and the return value is returned back as the return value of SyncProcessMessage.
      Asynchronous calls are more complicated. The call to AsyncProcessMessage is passed the message that represents the message call and a reference to a message sink object that is called when the reply is made. It is this message sink object that is returned by BeginInvoke. AsyncProcessMessage returns an IMessageCtrl interface which has a single method, Cancel, used to cancel the async call. There is no way that an async delegate can call this method.
      You can get information about the call by calling the methods on IAsyncResult. You can check to see if the call has completed either by checking the IsCompleted property, or by calling WaitOne on the AsyncWaitHandle property. Be aware, however, that if you use the overloaded version of WaitOne without a timeout, this call will block the current thread until the method has completed.

Figure 10 Calling an Object Asynchronously
Figure 10 Calling an Object Asynchronously

      This represents one pattern to call methods asynchronously: start the call and then poll or wait for completion (see Figure 10). When the call has completed, you can then call EndInvoke to get the results from the method. EndInvoke will return the return value of the method. If the method has out or ref values, EndInvoke will have parameters for those, too. The top part of the code in Figure 11 calls the delegate and polls for completion, and the code at the bottom waits for completion.
      If the thread that calls the delegate terminates before the method call completes, an exception will be thrown. Thus you should always ensure that this does not happen, and if necessary block the thread with a call to AsyncWaitHandle.WaitOne as you just saw in Figure 11. If any exceptions are thrown by the method invoked through the delegate, these will be handed back to the thread that called the asynchronous delegate.
      The final way to call a delegate asynchronously is to use the AsyncCallback parameter of the BeginInvoke method. This allows the calling code to provide a callback method which the async thread will call when the method has completed. This is equivalent to the code that was shown in Figure 7. The AsyncCallback method has an IAsyncResult parameter, which this time really refers to the actual result. However, you must have a reference to the delegate to be able to call EndInvoke and to do this you can cast the IAsyncResult parameter to get the AsyncResult object. The AsyncDelegate property of this object is the delegate that was called (see Figure 12).
      Of course, the method used to initialize the AsyncCallback delegate does not have to be on the same object as the code that calls the delegate. In this situation you may want to pass additional data to the callback. To do this, you can use the final parameter of the BeginInvoke method. This can be any object you choose, and it is accessed in the EndInvoke method through the AsyncState property of IAsyncResult.
      If the method that is called has a ref parameter, then the BeginInvoke and EndInvoke methods will both have ref parameters as well. A ref parameter is in/out; however, don't be fooled by this because the parameter will be updated asynchronously. To do this you must call the corresponding EndInvoke method; you cannot assume that the parameter you pass will be magically updated.
      For example, Figure 13 shows a delegate that has a ref parameter. This is used to call the times2 method asynchronously. In the run method, I wait for the call to complete, then print out the value of the variable I passed in as the ref parameter. If the method had been called synchronously, then this variable will be updated with the new value. For asynchronous calls, this is not the case. The first call to WriteLine will print out a value of 42, the input value. To get the return value, I must call EndInvoke.
      If the method parameters are reference types and are passed in as parameters, then the asynchronous method will be able to call methods on the reference types that change its state. You can use this to return values from the async method. Figure 14 shows an example of this. The GetData delegate takes an object of type System.Array as an in parameter. Since this is passed by reference, the method can change the values in the array. Since the method does not return a value, there is no need to call EndInvoke; the calling code can access the values in the array. However, since the object can be accessed by two threads, you have to make sure that you do not access this shared object until the asynchronous method has completed.
      If the delegate has a void return type, the code that invokes the delegate does not care about the return value from the method call. To help the remoting infrastructure, you should mark the method with the System.Runtime.Remoting.Messaging.OneWayAttribute attribute. If the method marked with this attribute throws an exception, the system will eat the exception rather than propagate it to the calling thread via the EndInvoke method.

Using Asynchronous Calls

      The beautiful thing about .NET asynchronous calls is that any method can be called this way. All you have to do is define an appropriate delegate and call that delegate with BeginInvoke and EndInvoke. You do not have to think about creating threads, nor do you have to worry about how parameters are passed or how results are returned; the asynchronous infrastructure will do it all for you.
      Some of the .NET classes in the base class library are designed specifically to be called asynchronously without the use of a delegate. The System.IO.Stream class is the base class of several of these classes, so I will use it as an example. The asynchronous methods of System.Stream are shown here:
public virtual IAsyncResult BeginRead(
   byte[] buffer, int offset, int count, 
   AsyncCallback callback, object state);

public: virtual int EndRead(IAsyncResult asyncResult);

public virtual IAsyncResult BeginWrite(
    byte[] buffer, int offset, int count,
    AsyncCallback callback, object state);

public virtual void EndWrite(IAsyncResult asyncResult);
These methods take a reference to a byte array that you allocate. Because you are passing a reference, the stream object will fill the object that lives in your code. You must take heed of the notes that I mentioned earlier. Your calling code must only access this object once it is sure that the async thread has finished using it, for example by making sure that IAsyncResult.IsCompleted is true.
      If you choose to access the data in an AsyncCallback, then the callback method is called when the async read or write has completed. This callback is called by the async thread, so you have to make sure that after a thread calls BeginRead it doesn't access the buffer. The callback will need to have access to the buffer, which you can provide through the final parameter of BeginRead. As I mentioned earlier, the state object passed through this parameter can be accessed as the IAsyncResult.AsyncState. The code in Figure 15 accesses a Web page asynchronously using buffers of 1000 bytes through the NetworkStream class.

Conclusion

      .NET has asynchronous code built in. You do not need to write special code to allow your methods to be called asynchronously. Some of the .NET base class library classes are designed to be called asynchronously, and by calling the appropriate BeginXxx and EndXxx methods the class will ensure that the method is called on another thread. Other classes that do not have async methods can be called asynchronously through a delegate. Delegate classes are generated by the C# compiler and are implemented by the runtime. The runtime ensures that there is a separate thread to make the call and provides synchronization objects to allow your calling code to test when the call has completed. Asynchronous calls involve multithreaded code, so you need to take this into account when passing shared object references.
Richard Grimes is the author of several books on COM and ATL for Wrox Press, including Professional ATL COM Programming and Professional Visual C++ 6 MTS Programming. His book on .NET development will be published by Addison-Wesley later this year. Reach Richard at atl.dev@grimes.demon.co.uk.

Page view tracker