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:
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:
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
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
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.
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.
The examples above showed two ways for completing asynchronous calls. There are four ways in which asynchronous calls can be completed:
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.