Asynchronous Calls in .NET

Asynchronous Calls in .NET

hen you make a method call on an object, typically you must block the client while the object executes the call, and control returns to the client only when the method completes execution and returns.

However, there are quite a few cases where you want to call methods asynchronously?that is, you want control to return immediately to the client while the object executes the called method in the background, and then somehow let the client know when the method execution is completed. Such an execution mode is called asynchronous method invocation and the action is an asynchronous call. Asynchronous calls allow you to improve availability, increase throughput and performance, and make your applications more scalable.

Standard, Consistent Mechanism
In the past, developers often had to create their own proprietary mechanism to asynchronously invoke calls on their components. One recurring mechanism was to have the object spin off a worker thread to process the client’s request and immediately return control to the client. The object would later signal the client somehow when the call completed (if the client wanted to know), and the client had to distinguish between multiple method completions. These mechanisms were difficult to develop and test, and they forced developers to spend a disproportionate amount of their time reinventing the wheel instead of adding business value to the application. In addition, such solutions coupled the clients to the objects, and were not consistently designed or implemented. Different vendors provided slightly different solutions, requiring at times different programming models on the client side. This predicament diminished the benefits of component-oriented programming because the component developer had to make some assumptions about the client’s way of using the component, and vice-versa.

The .NET mechanism for asynchronous calls is a mainstream facility used consistently and pervasively across the .NET application frameworks and base classes. .NET asynchronous calls are an essential addition to your arsenal as a component developer. Implementing robust asynchronous execution on your own is a demanding task, requiring a lot of effort spent on design, implementation, and testing.

Asynchronous calls allow you to improve availability, increase throughput and performance, and scale up your application

Requirements for an Asynchronous Mechanism
To make the most of the various options available with .NET asynchronous calls, you first need to understand the generic requirements set for any modern, component-oriented asynchronous calls support.

Use the same component code for both synchronous and asynchronous invocation. This allows the component developer to focus on the business logic, and facilitates using a standard mechanism.

A corollary of the first requirement is that you let the client decide whether to use the component synchronously or asynchronously. That, in turn, implies that the client will have different code for each case (whether to invoke the call synchronously or asynchronously).

The client should be able to issue multiple asynchronous calls, and manage multiple asynchronous calls in progress. The client should be able to distinguish between multiple methods as they complete their tasks.

By that same token, the component should be able to serve multiple concurrent calls.

When component methods have out-going parameters or return values, these parameters are not available when control returns to the client. The client should have a way of getting these parameters or results when the method completes.

Similarly, errors on the component’s side should be propagated to the client side. An exception thrown when the method executes should be played back to the client later on.

This last item is less of a requirement and more of a design guideline: The asynchronous calls mechanism should be straightforward and simple to use. For example, the mechanism should hide its implementation details as much as possible, such as the worker threads used to dispatch the call.

The client has a variety of options for handling method completion, all of which support these requirements. The client issues an asynchronous method call, and then can choose to:

  • Perform some work while the call is in progress, and then block until completion.
  • Perform some work while the call is in progress, and then poll for completion.
  • Receive notification when the method has completed. The notification will be in the form of a callback on a client-provided method. The callback should contain information identifying which method has just completed and its return values.
  • Perform some work while the call is in progress, and then wait only for a pre-determined amount of time and then stop waiting, even if the method execution has not completed yet.
  • Wait simultaneously for completion of multiple methods. The client can choose to wait for any or all of the pending calls to complete.

.NET offers all these options to clients, which can be confusing when you first start using asynchronous calls. This article will demonstrate each option and recommend when (or if) to use them. First, though, we need to discuss delegates.

What Delegates Are, Actually
To the programmer, a delegate is nothing more than a type-safe method reference. The delegate (as the name implies) is used to delegate the act of calling a method on an object (or a static method on a class) from the client to the delegate class. For example, consider a Calculator class:

   public class Calculator   {      public int Add(int num1,int num2)      {         return num1+num2;      }      public int Subtract(int num1,int num2)      {         return num1-num2;      }      //Other methods   }  

Instead of calling the Add() method directly, you can define a delegate called BinaryOperation:

   public delegate int BinaryOperation(       int num1,int num2);

You can use BinaryOperation to invoke the method:

   Calculator calc = new Calculator();   BinaryOperation oppDel;   oppDel = new BinaryOperation(calc.Add);    int result = 0;   result = oppDel(2,3);   Debug.Assert(result == 5);

By default, when you use a delegate to invoke methods, the delegate blocks the caller until all target methods return. In the example just shown, the caller is blocked until Add() returns. However, the delegate can also be used to invoke its target method asynchronously. The truth is that there isn’t really anything special about delegates because delegates are actually compiled to classes. When you define a delegate type, the compiler converts the delegate declaration to a sophisticated signature-specific class definition, and inserts that class instead of the delegate definition. For example, instead of this delegate definition:

   public delegate int BinaryOperation(int num1,int num2);

The compiler generates this class definition:

   public class BinaryOperation: System.MulticastDelegate   {      public BinaryOperation(Object target,         int methodPtr)         {...}      public virtual int Invoke(int num1,int num2)         {...}      public virtual IAsyncResult BeginInvoke(         int num1,int num2, AsyncCallback callback,         object asyncState)         {...}      public virtual int EndInvoke(         IAsyncResult result)         {...}   }

You can use the delegate simply to invoke a method, such as in this code.

   Calculator calc = new Calculator();   BinaryOperation oppDel;   oppDel = new BinaryOperation(calc.Add);   oppDel(2,3);

When you do that, the compiler converts the call to oppDel(2,3) to a call to the Invoke() method. The Invoke() method blocks the caller, executes the method on the caller’s thread, and returns control to the caller.

The compiler-generated BinaryOperation class derives from a class called MulticastDelegate defined in the System namespace. The compiler also declares two methods used to manage asynchronous method invocation. These methods are BeginInvoke() and EndInvoke(), and the proper use of them is the subject of this article.

Asynchronous Calls Programming Models
To support asynchronous invocation, multiple threads are required. However, it would be a waste of system resources and a performance penalty if .NET spun off a new thread for every asynchronous method invocation. A better approach is to use the .NET thread pool. The .NET way of supporting asynchronous calls is to hide this interaction completely. There are quite a few programming models available when dealing with asynchronous calls, all of which comply with the general requirements set at the beginning of this article. In general, use BeginInvoke() to initiate an asynchronous method invocation. The calling client is blocked only for the briefest moment, the time it takes to queue up a request for a thread from the thread pool to execute the method, and then control returns to the client. You use EndInvoke() to manage method completion, specifically, retrieving out-going parameters and return values as well as error handling.

Using BeginInvoke() and EndInvoke()
The compiler-generated BeginInvoke() and EndInvoke() methods take this general form:

   public virtual IAsyncResult BeginInvoke(      ,      AsyncCallback callback,      object asyncState);   public virtual  EndInvoke(      ,      IAsyncResult asyncResult);

BeginInvoke() accepts the parameters of the original signature that the delegate defines. These parameters include: value types, passed by reference (using out or ref modifiers), and reference types. The original method’s return values and any explicit outgoing parameters are part of the EndInvoke() method. For example, for this delegate definition:

   public delegate string MyDelegate(int num1,      out int num2, ref int num3,      object obj);

The corresponding BeginInvoke() and EndInvoke() methods would look like this:

   public virtual IAsyncResult BeginInvoke(                              int num1,                              out int num2,                              ref int num3,                              object obj,                              AsyncCallback callback,                              object asyncState);   public virtual string EndInvoke(                          out int num2,                          ref int num3,                          IAsyncResult asyncResult);

BeginInvoke() accepts two additional parameters that were not present in the original delegate signature: AsyncCallback callback and object asyncState. The callback parameter is actually a delegate object representing a reference to a callback method used to receive the method completed notification event. asyncState is a generic object used to pass in whatever state information is needed by the party handling the method completion. These two parameters are optional in that the caller can choose to pass in null instead of either one of them. For example, to asynchronously invoke the Add() method of the Calculator class, if you have no interest in the result, and no interest in a callback method or state information, you would write:

   Calculator calc = new Calculator();   BinaryOperation oppDel;   oppDel = new BinaryOperation(calc.Add);   oppDel.BeginInvoke(2,3,null,null);

The object itself is unaware that the client is using a delegate to asynchronously invoke the method. The same object code handles both the synchronous and the asynchronous invocation cases. As a result, every .NET class supports asynchronous invocation.

The .NET mechanism for asynchronous calls is a mainstream facility used consistently and pervasively across the .NET application frameworks and base classes

Because you can use delegates on both instance methods and static methods, clients can use BeginInvoke() to asynchronously call static methods as well. The remaining question is, how would you get the results of the method?

The IAsyncResult Interface

Every BeginInvoke() method returns an object implementing the IAsyncResult interface, defined as:

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

You will see a few uses for the properties of IAsyncResult later on. For now, it is sufficient to know that the returned IAsyncResult object uniquely identifies the method that was invoked using BeginInvoke(). You can pass the IAsyncResult object to EndInvoke() to identify the specific asynchronous method execution from which you wish to retrieve the results. Listing 1 shows the entire sequence.

As simple as Listing 1 is, it does demonstrate a few key points. The most important of them is that because you primarily use EndInvoke() to retrieve any outgoing parameters as well as the method return value, EndInvoke() will block its caller until the method it waits for (identified by the IAsyncResult object passed in) returns. The second point is that you can use the same delegate object (with exactly one target method) to invoke multiple asynchronous calls on the target method. The caller can distinguish between the different pending calls using each unique IAsyncResult object returned from BeginInvoke(). In fact, when the caller invokes asynchronous calls, as in Listing 1, the caller must save the IAsyncResult objects. In addition, the caller should make no assumption about the order in which the pending calls complete. Remember?the asynchronous calls are carried out on threads from the thread pool, and because of thread context-switches (as well as internal pool management), it is possible the second call will complete before the first one.

There are other uses for the IAsyncResult object besides passing it to EndInvoke(). You can use it to get the state object parameter of BeginInvoke(). You can wait for the method completion. Or, you can get the original delegate used to invoke the call. You will read how to use these later on.

Although it is not evident in Listing 1, there are three important programming points you must always remember when you use delegate-based asynchronous calls:

You can only call EndInvoke() once for each asynchronous operation. Trying to call EndInvoke() more than once will result in an exception of type InvalidOperationException.

Although the compiler-generated delegate class can manage multiple targets, when you use asynchronous calls, the delegate can only have exactly one target method in its internal list. If you call BeginInvoke() when the delegate’s list has more than one target, you’ll get an ArgumentException stating that the delegate must have only one target.

You can only pass the IAsyncResult object to EndInvoke() on the same delegate object used to dispatch the call. Passing the IAsyncResult object to a different delegate would result in an exception of type InvalidOperationException stating, “The IAsyncResult object provided does not match this delegate.” Listing 2 demonstrates this point. It results in the exception, even though the other delegate targets the same method.

The AsyncResult Class

Often, one client will initiate the asynchronous call and another client will call EndInvoke(). Even if only one client is involved, it is likely that it will call BeginInvoke() in one code section (or method) and EndInvoke() in another. It is bad enough you have to either save the IAsyncResult object or pass it around between clients. It would be even worse if you have to do the same for the delegate object used to invoke the asynchronous call, just because you need that delegate to call EndInvoke() on. As it turns out, there is an easier solution. The IAsyncResult object itself carries with it the delegate that created it. When BeginInvoke() returns the IAsyncResult reference, it is actually an instance of a class called AsyncResult, defined as:

   public class AsyncResult : IAsyncResult, IMessageSink   {     //IAsyncResult implementation      public object AsyncState {virtual get;}     public WaitHandle AsyncWaitHandle{virtual get;}      public bool CompletedSynchronously{virtual get;}     public bool IsCompleted {virtual get;}     //Other properties     public bool EndInvokeCalled{get; set;}     public object AsyncDelegate{virtual get;}     /* IMessageSink implementation  */   }

You’ll find AsyncResult in the System.Runtime.Remoting.Messaging namespace. AsyncResult has a property called AsyncDelegate, which is a reference to the original delegate used to dispatch the call. Listing 3 shows how to use the AsyncDelegate property to call EndInvoke() on the original delegate.

Note that because the AsyncDelegate property is of type object, you need to downcast it to the actual delegate type (see BinaryOperation in Listing 3).

Listing 3 demonstrates using another useful property of AsyncResult?the Boolean EndInvokeCalled property. You can use it to verify that EndInvoke() was not called yet:

   Debug.Assert(asyncResult.EndInvokeCalled == false);

Polling or Waiting for Completion
In the programming model just shown, when the client calls EndInvoke(), the client is blocked until the asynchronous method returns. This may be fine if the client has a finite amount of work to do while the call is in progress, and if, once that work is done, the client cannot continue its execution without the returned value or the output parameters of the method, or even just the knowledge that the method call has completed. However, what if the client only wants to check if the method execution is completed? What if the client wants to wait for completion for a fixed timeout, do some additional finite processing, and then wait again? .NET supports these alternative programming models to calling EndInvoke().

The IAsyncResult interface object returned from BeginInvoke() has the AsyncWaitHandle property, of type WaitHandle. WaitHandle is actually a .NET wrapper around a native Windows waitable event handle. WaitHandle has a few overloaded wait methods. For example, the WaitOne() method only returns once the handle is signaled. Listing 4 demonstrates using WaitOne().

The same object code handles both the synchronous and the asynchronous invocation cases

Logically, Listing 4 is identical to Listing 1, which only called EndInvoke(). If the method is still executing, then WaitOne() will block. If by the time WaitOne() is called, the method execution is complete, then WaitOne() will not block, and the client proceeds to call EndInvoke() for the returned value. The important difference between Listing 4 and Listing 1 is that the call to EndInvoke() in Listing 4 is guaranteed not to block its caller.

Listing 5 demonstrates a more practical way to use WaitOne(), by specifying a timeout condition (10 milliseconds in this example). When you specify a timeout, WaitOne() will return when the method execution completes or when the timeout has elapsed, whichever of these two conditions occurs first.

Listing 5 uses another handy property of IAsyncResult called IsCompleted, which lets you find out the status of the call without waiting or blocking. You can even use IsCompleted in a strict polling mode:

   while(asyncResult.IsCompleted == false)   {      /*Do some work */   }

This has all the adverse effects of polling (consuming CPU power for nothing), so I suggest you avoid using IsCompleted this way. The AsyncWaitHandle property really shows its usefulness when you use it to manage multiple concurrent asynchronous methods in progress. You can use the WaitAll() static method of the WaitHandle class to wait for completion of multiple asynchronous methods, as shown in Listing 6.

To use WaitAll() you need to construct an array of handles. Note that you still need to call EndInvoke() to access returned values. Instead of waiting for all the methods to return, you can choose to wait for any of them to return using the WaitAny() static method of the WaitHandle class:


Similar to WaitOne(), both WaitAll() and WaitAny() have a few overloaded versions, which let you specify a timeout to wait instead of waiting indefinitely.

Using the Completion Callback Method
Instead of blocking, waiting, or polling for the asynchronous method completion, .NET also offers another programming model altogether: callbacks. The idea is simple: the client provides .NET with a method, and requests that .NET call that method back when the asynchronous method completes. The client can provide a callback instance method or static method, and have the same callback method handle completion of multiple asynchronous methods. The only requirement is that the callback method has the following signature:

   void (IAsyncResult asyncResult);

The convention for a callback method name is to prefix it with On, for example, OnAsyncCallBack(), OnMethodCompletion(), and so on. Here is how the callback mechanism works: As explained previously, .NET uses a thread from the thread pool to execute the method dispatched via BeginInvoke(). When the asynchronous method execution is completed, instead of quietly returning to the pool, the worker thread calls the callback method.

To use a callback method, the client needs to provide BeginInvoke() with a delegate that targets the callback method. That delegate is provided as the next to last parameter to BeginInvoke(), and is always of type AsyncCallback. AsyncCallback is a .NET-provided delegate from the System namespace, defined as:

   public delegate void AsyncCallback(                           IAsyncResult asyncResult);

Listing 7 demonstrates asynchronous call management using a completion callback method.

Unlike the previous programming models, when you use a completion callback method, there is no need to save the IAsyncResult object returned from BeginInvoke(). Why? Because when .NET calls the callback method, .NET provides the IAsyncResult object as a parameter. Note in Listing 7 the use of a downcast of the IAsyncResult parameter to an AsyncResult class to get the original delegate used to dispatch the call. You need that delegate to call EndInvoke(). Because .NET provides a unique IAsyncResult object per asynchronous method, you can channel multiple asynchronous method completions to the same callback method, even using the same AsyncCallback delegate:

   Calculator calc = new Calculator();   AsyncCallback callback   callback = new  AsyncCallback(OnMethodCompletion);   BinaryOperation oppDel1;   BinaryOperation oppDel2;   oppDel1   = new  BinaryOperation(calc.Add);   oppDel1.BeginInvoke(2,3,callback,null);   oppDel2   = new  BinaryOperation(calc.Add);   oppDel2.BeginInvoke(4,5,callback,null);

Callback completion methods are by far the preferred model in any event-driven application. An event-driven application has methods that trigger events (or dispatch requests, post and process messages) and methods that handle these requests and fire their own events as a result. Writing an application as event driven makes it a lot easier to manage multiple threads, events, and messages, and allow for scalability, responsiveness, and performance. .NET asynchronous calls management using callback completion methods fits into such an architecture like a hand in a glove. The other options (waiting, blocking, polling) are available for completeness sake, for applications that are very strict, predictable, and deterministic in their execution flow. I recommend that you use completion callback methods whenever possible.

Passing State Information

I ignored the last parameter to BeginInvoke(), object asyncState, up until now. It is provided as a generic container for whatever you need to use it for. .NET calls this container a state object. The party handling the method completion can access the container objects via the object AsyncState property of IAsyncResult. Although you can certainly use a state object with any of the .NET asynchronous models (blocking, waiting, polling), it is most useful in conjunction with completion methods. The reason is simple: in all the other programming models, it is up to you to manage the IAsyncResult object, and managing an additional container is not that much of an added liability. When you are using a completion callback, the container object is the only way to pass in additional parameters to the callback method, because its signature is pre-determined by .NET. Listing 8 demonstrates using a state object to pass an integer value as an additional parameter to the completion callback method. Note that the callback method must downcast the AsyncState property to the actual type it expects.

Performing Asynchronous Operations without Delegates
Delegate-based asynchronous calls like those described in the preceding sections let you asynchronously invoke any method on any class. This provides valuable flexibility to a client, but requires that you define a delegate with a signature that matches the method you want to invoke. Sometimes, certain operations such as disk or network access, Web requests, Web service calls, or message queuing are long in duration or even open-ended in their very nature. In such cases, you will usually opt to invoke the operations asynchronously. The designers of the .NET Framework wanted to ease the task of performing such operations asynchronously by building methods into the classes that offer them Begin and End. These methods always take a form very similar to the BeginInvoke() and EndInvoke() methods provided by a delegate class:

   public  ();   IAsyncResult Begin(              ,              AsyncCallback callback,              object asyncState);    public  End(                             IAsyncResult asyncResult);

For example, the abstract Stream class, defined in the System.IO namespace, provides asynchronous Read() and Write() operations:

   public class Stream : MarshalByRefObject,                         IDisposable   {      public virtual int Read(byte[]buffer,                              int offset,int count);      public virtual IAsyncResult BeginRead(                              byte[]buffer,                              int offset,                              int count,                              AsyncCallback callback,                              object state);      public virtual int EndRead(                           IAsyncResult asyncResult);      public virtual void Write(byte[]buffer,                              int offset,int count);      public virtual IAsyncResult BeginWrite(                              byte[]buffer,                              int offset,                              int count,                              AsyncCallback callback,                              object state);      public virtual void EndWrite(                           IAsyncResult asyncResult);      /* Other methods and properties */   }

The Stream class is the base class for all other stream classes, such as FileStream, MemoryStream, and NetworkStream. All the Stream-derived classes override these methods and provide their own implementation.

Another example of a class that provides its own asynchronous methods is the Web service wrapper class that Visual Studio .NET automatically adds to a client project when the client adds a reference to a Web service. Imagine that the Calculator class in the following code snippet exposes its methods, such as Add(), as Web services:

   using System.Web.Services;   public class Calculator   {      [WebMethod]      public int Add(int num1,int num2)      {         return = num1+num2;      }      //Other methods   }

The wrapper class auto-generated for the client by Visual Studio .NET will contain BeginAdd() and EndAdd() methods used to invoke the Web service asynchronously:

   using System.Web.Services.Protocols;   public class Calculator : SoapHttpClientProtocol   {      public int Add(int num1,int num2)       {...}      public IAsyncResult BeginAdd(int num1,int num2,         AsyncCallback callback,object asyncState)       {...}      public int EndAdd(IAsyncResult asyncResult)       {...}      /* Other methods  */   }

Using non-delegate-based asynchronous method calls is similar to using the BeginInvoke() and EndInvoke() methods provided by a delegate class: you dispatch the asynchronous operation using Begin and can either call End to block until completion, wait for the operation (or multiple operations) to complete, or use a callback method. However, there is no uniform requirement to call End on the original object that you used to dispatch the Begin call. With some classes (like Web service wrapper classes or Stream-derived classes), you can create a new object and call End on it. Listing 9 demonstrates this technique when using a Web service wrapper class.

Listing 10 demonstrates asynchronous read operation on a FileStream object. Note that you pass the useAsync parameter to the FileStream constructor, indicating asynchronous operations on the stream.

Asynchronous Error Handling
Output parameters and returned values are not the only thing unavailable at the time an asynchronous call is dispatched?exceptions are missing as well. After calling BeginInvoke(), control returns to the client, but it may be some time until the asynchronous method encounters an error and throws an exception, and it may be some time after that until the client actually calls EndInvoke(). .NET must therefore provide for some way for the client to know that an exception was thrown and for the client to handle it. The .NET solution is straightforward: When the asynchronous method throws an exception, .NET catches that exception. When the client calls EndInvoke(), .NET re-throws that exception object, letting the client handle the exception. If a callback method is provided, .NET calls the callback method immediately after the exception is thrown on the object side. For example, suppose the Calculator class has a Divide() method, defined as:

   public class Calculator   {      public int Divide(int num1,int num2)      {         return = num1/num2;      }      //Other methods   }  

Divide() will throw a DivideByZeroException if the denominator (num2) passed in is zero. Listing 11 demonstrates how you might handle this error.

By providing you with asynchronous calls support, .NET lets you focus on the domain problems at hand rather than on complicated asynchronous plumbing. You can use delegate-based asynchronous invocation on any type, be it .NET or your own, and once you have mastered it, asynchronous calls are consistent across all cases, projects, and teams. You can even build-in such support into your own types if you anticipate the need for asynchronous calls (like the Stream class does), and use delegates internally to implement the asynchronous call invocation.

See also  Comparing different methods of testing your Infrastructure-as-Code

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist