Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / operating-systems / Windows

The Power of the Asynchronous Programming Model as Implemented by Delegates

3.54/5 (20 votes)
21 Jun 2006CPOL3 min read 1  
The Asynchronous Programming Model (APM) is implemented by Delegates, allowing you to easily invoke any method asynchronously

Preface

This article is written for Microsoft .NET 2.0 and Visual Studio 2005. For information on changes required by v.1.1 and Visual Studio 2003, please check the comments.

Asynchronous Programming Model

The Asynchronous Programming Model (APM), as implemented by delegates, consists of three parts:

  1. BeginInvoke
  2. EndInvoke
  3. Rendezvous techniques

BeginInvoke starts an algorithm, implemented via a method, on a new thread.
EndInvoke retrieves the result of that method.
The Rendezvous techniques allow you to determine when the asynchronous operation has completed.

Rendezvous Techniques

There are three different types of Rendezvous techniques you can use to retrieve the results of an asynchronous delegate invocation. The first is Wait-Till-Completion, implemented via EndInvoke. Calling this method will block the current thread until the results of the asynchronous method are available. This is the least effective method, as it virtually eliminates the benefits of APM.

C#
// a delegate for a method that takes no params and returns a string 
private delegate string StringReturningDelegate();
private void Main()
{
    // create an instance of the delegate pointing to a method 
    // that takes ten seconds to complete     
    StringReturningDelegate fd = 
        new StringReturningDelegate(MethodThatTakes10SecondsToComplete);
    // Begin invocation of this delegate     
    IAsyncResult receipt = fd.BeginInvoke(null, null);
    // Immediately call EndInvoke, which will block for, oh, say, 
    // right about ten seconds     
    string result = fd.EndInvoke(receipt);
    Console.Write(result);
    Console.Read();
}
// A method that takes 10 seconds, then returns a string 
private string MethodThatTakes10SecondsToComplete() 
    { Thread.Sleep(10000); return "Done!"; }

This code segment demonstrates Wait-Till-Completion. You can see that it offers no benefits over calling MethodThatTakes10SecondsToComplete synchronously, as the EndInvoke method will block the calling thread.

The second Rendezvous technique is called Polling. In this technique, you check a property of the IAsyncResult object is called IsCompleted. This property will return false until the async operation has completed. The following code segment demonstrates polling on the same method:

C#
// a delegate for a method that takes no params and returns a string
private delegate string StringReturningDelegate();
private void Main()
{
    // create an instance of the delegate pointing to a method 
    // that takes ten seconds to complete    
    StringReturningDelegate fd = 
        new StringReturningDelegate(MethodThatTakes10SecondsToComplete);
    // Begin invocation of this delegate                
    IAsyncResult receipt = fd.BeginInvoke(null, null);
    Console.Write("Working");
    // Poll IsCompleted until it returns true; 
    // Sleep the current thread between checks to reduce CPU usage    
    while (!receipt.IsCompleted)
    {
        Thread.Sleep(500);  // wait half a sec        
        Console.Write('.');
    }
    string result = fd.EndInvoke(receipt);
    Console.Write(result);
    Console.Read();
}
// A method that takes 10 seconds, then returns a string
private string MethodThatTakes10SecondsToComplete() 
    { Thread.Sleep(10000); return "Done!"; }

This method isn't much better. You sleep away all the extra time that you could have been productive with (like in college). If you wanted to, you could take advantage of the loop to show some kind of procedural animation to the user, thus keeping them informed and aware that your program hasn't locked up. This makes this technique a little more useful than Wait-Till-Completion.

The third, and most efficient, Rendezvous technique is Method Callback. In this technique, you pass a delegate to the BeginInvoke method that will be called when the asynchronous operation has completed. It will not block your execution, or waste any CPU cycles. You should always use this method of Rendezvous.

C#
// a delegate for a method that takes no params and returns a string
private delegate string StringReturningDelegate();  
private void Main()        
{                 
    // create an instance of the delegate pointing to a method 
    // that takes ten seconds to complete            
    StringReturningDelegate fd = 
        new StringReturningDelegate(MethodThatTakes10SecondsToComplete);     
    // Begin invocation of this delegate                 
    fd.BeginInvoke(AsyncOpComplete, null);    
    // Do tons of work here.  No, seriously.
    Console.Read();        
}
/// <summary>
/// Retrieves the results of MethodThatTakes10SecondsToComplete 
/// when called asynchronously
/// </summary>
/// <param name="receipt">The IAsyncResult receipt.</param>
private void AsyncOpComplete(IAsyncResult receipt)
{
    //  Cast to the actual object so that we can access the delegate
    AsyncResult result = (AsyncResult)receipt;
    //  retrieve the calling delegate
    StringReturningDelegate gsld = (StringReturningDelegate)result.AsyncDelegate;
    //  Retrieve our results; this is guaranteed not to block, 
    //  as the async op is complete
    string result = gsld.EndInvoke(receipt);
    //  write the result to the console
    Console.Write(result);
}
// A method that takes 10 seconds, then returns a string        
private string MethodThatTakes10SecondsToComplete() 
    { Thread.Sleep(10000); return "Done!"; }

This method allows the program to continue execution while the async operation completes on another thread. When the operation completes, the delegate will call the AsyncOpComplete method, which was passed to the delegate via the BeginInvoke method.

No Changes Required

Take note that the implementation of MethodThatTakes10SecondsToComplete has not changed. This is the major strength of the APM. You create the method you wish to call asynchronously just as you would if it were to be used synchronously. All the work required to call this method asynchronously is performed by the delegate. All you have to do is create a delegate that matches the signature of your method, and construct a method (that returns void and takes one IAsyncResult parameter) designed to be run upon completion of the async operation.

Limitations of the APM in .NET

APM via delegates is an extremely useful and agile tool that you can use to make your programs run faster and be more responsive, but with power comes responsibility. Improper usage may leak resources. For every BeginInvoke call, you must call EndInvoke to prevent this. Additionally, you must understand that asynchronous operations can be less efficient than fast, synchronous operations. Save them for I/O bound operations or compute bound operations that you know will take time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)