Asynchronous Calls

Summary

Overview

When an asynchronous call is made to a .NET class method, the method returns immediately and program execution continues. In the background, the asynchronously called method is processed and when the call completes, it calls back a function whose details were supplied when the asynchronous method was first called. 

Asynchronous programming is a feature supported by many areas of the .NET Framework:

Asynchronous Programming Design Pattern

In the old days of Win32 programming, if you wanted to call a method asynchronously, you either had to spin a worker that thread that called the method synchronously, or the called method itself had to be written so that it supports asynchronous calls. In .NET this is no longer necessary - a called method on a .NET object does not need to do any additional programming to support asynchronous calls. This is all taken care of using asynchronous delegates. With .NET asynchronous programming, the compiler will split the asynchronous operation to two logical parts:

  1. BeginInvoke: This first part takes input from the client and an AsyncCallBack delegate object and then starts the asynchronous operation. The AsyncCallBack delegate specifies a function to be called back when the asynchronous call completes. This first part returns a waitable object that implements the IAsyncResult interface that can be used by the client to determine the status of the call.
  2. EndInvoke: This second part uses the waitable object (IAsyncResult-derived) that was returned in the first call to supply results of the asynchronous operation back to the client

Usage

Example 1

To setup methods for asynchronous invocations:

namespace AsyncProcessing
{
    // Step A2: Declare a delegate for Order.ProcessOrder which is the function that will be called asynchronously
   
public delegate bool ProcessOrderDelegate( int nOrderID, string strCustomer, ref int nShipmentID);

    public class Order
    {
        // Step A1: This function will be called asynchronously
       
public bool ProcessOrder( int nOrderID, string strCustomerName, ref int nShipmentID )
        {
            Trace.WriteLine( "Starting ProcessOrder " + nOrderID.ToString() + " for customer " + strCustomerName );
            Trace.WriteLine( "(Thread pool: " + System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() + ")");

            // Update some parameters
    
        nShipmentID = 456;
            bool bProcessed = true;

            // Do some other work ...

            Trace.WriteLine( "Ending ProcessOrder " + nOrderID.ToString() + " for customer " + strCustomerName );
            return bProcessed;
        }

    // Step A3: This is the callback function that will be called back when the asynchronous operation completes
    // Its signature is as required by AsyncCallBack - the delegate that wraps the callback function
    [OneWay]
    public void ProcessOrderCallBack(IAsyncResult ar )
    {
        Trace.WriteLine( "Starting ProcessOrderCallback " );
        Trace.WriteLine( "(Thread pool: " + System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() + ")");

        // Obtain the result of the asynchronous call by retrieving the delegate of the function
        // that was called asynchronously
        ProcessOrderDelegate pod = (ProcessOrderDelegate)((AsyncResult)ar).AsyncDelegate;

        // Now retrieve results
        int nShipmentID = 0;
        bool bProcessed = false;
        bProcessed = pod.EndInvoke( ref nShipmentID, ar );

        Trace.WriteLine( "Ending ProcessOrderCallback. ShipmentID = " + nShipmentID.ToString() + ", bProcessed = " + bProcessed.ToString() );
    }
}

To call a method asynchronously

static void Main(string[] args)
{
    // Call a function asynchronously
    AsyncProcessingApproach1();

    Console.ReadLine();
}

public static void AsyncProcessingApproach1()
{
    // Create an instance of the Order class
    Order order = new Order();

    // Step B1: Create an instance of the AsyncCallBack delegate that will be wrap the method to be called
    // when the asyncrhonous operation completes
    AsyncCallback acb = new AsyncCallback( order.ProcessOrderCallBack );


    // Step B2: Create an instance of the ProcessOrderDelegate delegate that will be used to call a method
    // synchronously
    ProcessOrderDelegate pod = new ProcessOrderDelegate( order.ProcessOrder );


    // Step B3: Now call the ProcessOrder method asynchronoulsy via the ProcessOrderDelegate delegate
    int nShipmentID = 0;
    IAsyncResult ar = pod.BeginInvoke( 123, "Yazan", ref nShipmentID, acb, null );

    // Do some other work ...
}

Output. Note how the ProcessOrder completes and then ProcessOrderCallback gets called. Also note that both the function called asynchronously (ProcessOrder) and the callback function (ProcessOrderCallback) are both called on the thread pool:

Starting ProcessOrder 123 for customer Yazan
(Thread pool: True)
Ending ProcessOrder 123 for customer Yazan
Starting ProcessOrderCallback 
(Thread pool: True)
Ending ProcessOrderCallback. ShipmentID = 456, bProcessed = True

Example 2

This example is exactly the same as above, except that there is no AsyncCallBack delegate. Instead, we explicitly wait for the asynchronous function to finish rather than depend on having a function being called back when execution finishes:

public static void AsyncProcessingApproach2()
{
    // Create an instance of the Order class
    int nShipmentID = 0;
    bool bProcessed = false;
    Order order = new Order();

    // Create an intance of the ProcessOrderDelegate delegate that will be used to call a method
    // synchronously
    ProcessOrderDelegate pod = new ProcessOrderDelegate( order.ProcessOrder );

    // Now call the ProcessOrder method asynchronoulsy via the ProcessOrderDelegate delegate
    Trace.WriteLine( "Calling asynchronous function ... ");
    IAsyncResult ar = pod.BeginInvoke( 123, "Yazan", ref nShipmentID, null, null );

    // Now we actively wait for the asynchronous function to finish
    Trace.WriteLine( "Waiting for asynchronous function to finish ... ");
    ar.AsyncWaitHandle.WaitOne();
    if (ar.IsCompleted)
    {
        Trace.WriteLine( "synchronous function finished. Retrieving data ... ");
        bProcessed = pod.EndInvoke( ref nShipmentID, ar );
        Trace.WriteLine( "New data: ShipmentID = " + nShipmentID.ToString() + ", bProcessed = " + bProcessed.ToString() );
    }
}

Output is shown below:

Calling asynchronous function ... 
Waiting for asynchronous function to finish ... 
Starting ProcessOrder 123 for customer Yazan
(Thread pool: True)
Ending ProcessOrder 123 for customer Yazan
synchronous function finished. Retrieving data ... 
New data: ShipmentID = 456, bProcessed = True

Pattern

Asynchronous delegates provide the ability to call synchronous methods asynchronously. When calling a delegate synchronously, the Invoke method calls the target method directly on the current thread. If the compiler supports asynchronous delegates, it will generate BeginInvoke and EndInvoke with the proper signatures for the target method (i.e., the method to be called asynchronously). When BeginInvoke is called, the CLR will queue the request to run the target method on the thread pool and return immediately to the caller. The original thread which submitted the request is free to continue running in parallel to the thread pool. If a callback has been specified in BeginInvoke, it will be called when the target method (i.e., the method to be called asynchronously) returns. In the target method, EndInvoke is used to obtain the return value and in/out/ref parameters. If a callback has been not specified in BeginInvoke, EndInvoke can be used on the original thread which queued the request to retrieve values.

Compiler and CLR Support

Recall that in Example 1, we set up methods to be called asynchronously as follows:

namespace AsyncProcessing
{
    // Step A2: Declare a delegate for Order.ProcessOrder which is the function that will be called asynchronously
   
public delegate bool ProcessOrderDelegate( int nOrderID, string strCustomer, ref int nShipmentID);

    public class Order
    {
        // Step A1: This function will be called asynchronously
       
public bool ProcessOrder( int nOrderID, string strCustomerName, ref int nShipmentID )
        {
            ...
        }

    // Step A3: This is the callback function that will be called back when the asynchronous operation completes
    // Its signature is as required by AsyncCallBack - the delegate that wraps the callback function
    [OneWay]
    public void ProcessOrderCallBack(IAsyncResult ar )
    {
        ...
    }
}

When the compiler emits the ProcessOrderDelegate delegate class, it will generate BeginInvoke and EndInvoke methods using the asynchronous method signatures, in addition to the Invoke method:

public class ProcessOrderDelegate : delegate
{
    public bool          Invoke( 
                            int             nOrderID,
                            string          strCustomerName,
                            ref int         nShipmentID );

    // The following code was supplied by the compiler
    public IAsyncResult BeginInvoke(
                            int             nOrderID,
                            string          strCustomerName,
                            ref int         nShipmentID,
                            AsyncCallBack acb,
                            Object        AsyncState );

    // The following code was supplied by the compiler
    public bool          EndInvoke(
                            ref int       nShipmentID,
                            IAsyncResult  ar );
}

AsyncCallBack is a delegate the specifies the method that should be called back when the asynchronous operation completes. This call back method will be called on a thread from the thread pool.

IAsyncResult interface is used to monitor and manage the asynchronous operation. It is returned from the BeginInvoke operation and passed to the EndInvoke operation to correlate the begin and the end.

Completing Calls

The examples above showed two ways for completing asynchronous calls. There are four ways in which asynchronous calls can be completed:

Asynchronous Remoting

Asynchronous programming in a remoting scenario is identical to asynchronous programming in a single application domain or context. The only difference is reading .NET Remoting configuration information. Asynchronous programming in a remoting scenario has been discussed in In-Depth .NET Remoting under Using Asynchronous Calls.