The Power of the Asynchronous Programming Model as Implemented by Delegates

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:

BeginInvoke

EndInvoke

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 delegateinvocation. 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.

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 falseuntil the async operation has completed. The following code segment demonstrates polling on the same method:

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 delegateto 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.

// a delegate for a method that takes no params and returns a string
privatedelegatestring StringReturningDelegate();
privatevoid 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();
}
///<spanclass="code-SummaryComment"><summary></span>/// Retrieves the results of MethodThatTakes10SecondsToComplete
/// when called asynchronously
///<spanclass="code-SummaryComment"></summary></span>///<spanclass="code-SummaryComment"><paramname="receipt">The IAsyncResult receipt.</param></span>privatevoid 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
privatestring 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 delegatethat matches the signature of your method, and construct a method (that returns voidand 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.

How is the third method use you describe, of providing a delegate function to be called at the end of the thread, any different from having a thread post a registered windows message and then exit/terminate and have the originating thread handle the registered message?

Additionally there are occassions where it will make sense to effectively 'single thread' a collection of worker threads in order to guarantee dependancies that may exist. And so I've seen code like this

The difference is that by posting the message you force the UI thread to service the async operation. That negates one of the benefits of asynch ops -- keeping your UI thread responsive to the user. If you need to do more processing prior to updating the UI, you may cause the app to show that lovely (not responding) message that users love oh so much.

I'm not saying that you should use async delegate invocation wherever possible. You have to program for the particular situation. Most of the time synchronous programming is more than sufficient; from there, ThreadPool.QueueUserWorkItem is fine, then comes async delegate invocation, and lastly instances of System.Threading.Thread. Use the tools that work best for your situation.

And both have different uses. But of course if you use EndInvoke the way you did it, without doing anything else between BeginInvoke and EndInvoke, it has no sense. But you could start a thread, continue doing something else and when ready use the EndInvoke to be sure the other thread has completed its task. And of course, given the threads do not compete over the same resources. If they do, there is no benefit on multithreading, except user interface response.

As far as I know, I haves no idea what you mean by "there is only two methods." If you are talking about methods of rendezvous, you are mistaken. As the example showed, you can use the IsCompleted property of the IAsyncResult interface to test if the async operation has completed. You can spinlock on this property until it returns true. That is, if you hate everybody who is running on the current machine.

Edgardo Menta wrote:

But of course if you use EndInvoke the way you did it, without doing anything else between BeginInvoke and EndInvoke, it has no sense.

Obviously, the examples use the least amount of code to demonstrate the point. I suppose I could have had put the code in a sample application called, oh, I don't know... Nitpick Generator, where after I invoked the delegate I spunlocked on a method that generated random nitpicky things which then printed to the screen until the callback method was run, which at that point would null out the nitpick generator and garbage-collect it... but I'm lazy. I apologize.

Edgardo Menta wrote:

And of course, "you should always use callback", is not true.

I could say, "You should never kick somebody in the jellies," which everybody would agree is true; however there are some times when you just have to do it. Just as in there are some times when you just have to call EndInvoke when its possible your calling thread will be blocked. However, if it doesn't matter that your calling thread (be it UI or other) be blocked, then why not just go with a synchronous operation? Async ops, tho they take advantage of multiprocessor machines, do cost in terms of time (to construct) and resources (additional memory pressure).
(modified)
I guess I was drowning in my own snarkiness to remember that you HAVE TO call EndInvoke for every BeginInvoke, otherwise you leak resources. The sole exception is Control.BeginInvoke, which will not leak resources if you don't call EndInvoke.

I did´nt mean your are lazy. I mean, that your sample shows what u said: EndInvoke is no useful, because it blocks your thread. But only because you do nothing between BeginInvoke and EndInvoke. But false premises, so false conclusions turns true. You could do something else between BeginInvoke and EndInvoke, so when u call EndInvoke, your thread is not blocked. Instead, you decided your thread should wait the new thread to finish (example: the new thread generates some data your thread needs, i mean data dependency, clearly the best one to have between threads if some). So "you should never kick somebody in the jellies" turns to be "you quite often kick somebody in the jellies". The callback method is quite useful when the calling thread is a user interface thread. But if not, maybe you don´t want to use the callback. And, as far as i know if you use IsComplete only to wait it says "true" inside a while, and then call EndInvoke, I don´t see any difference. Not saying that you are lazy, but that your example obscure the point, and contributes to mistaken conclusions. But of course, you don´t have to agree with me.

Actually, I am lazy, and you are right -- the point of APM is to do multiple things at the same time. So waiting for your async op to finish is pointless.

And, in the end, if you spinlock on IsCompleted until it returns true and then call EndInvoke it produces the same result (blocking your thread's progression) as just calling EndInvoke. But even though they produce the same result they are two different ways to get to that result. For instance, if you wish to show a progress bar (or some other animation) to the user while waiting for an async operation to finish, you could do the following:
class Program
{
private delegate string DoWorkDelegate();
static void Main(string[] args)
{
DoWorkDelegate dwd = new DoWorkDelegate(DoWork);
IAsyncResult receipt = dwd.BeginInvoke(null, null);
Console.WriteLine("Okay, let's figure out the answer");
Console.Write("Working");
while (!receipt.IsCompleted)
{
Thread.Sleep(500); // wait half a sec
Console.Write('.');
}
Console.WriteLine();
Console.Write(dwd.EndInvoke(receipt));
Console.Read();
}

As you can see, we are accomplishing something as we spinlock on the IsCompleted property. You cannot do this by calling EndInvoke, since it blocks the calling thread. That's why you can consider them to be two different types of rendezvous techniques. Hope that is more clear than the article. Good programming!

...In the third example: I believe there should be a delegate - right now there is only the name of a function.

True, but it works. There are some times that you can pass a method group as a delegate, and some times you cannot. I haven't figured out the rules behind it; I suppose the C# compiler knows what you're trying to do with some specifc types of delegates (events work the same exact way -- someObject.Event += SomeMethod;). This is one of those cases.

I'm using Microsoft Visual Studio 2003 (7.1.x) and to me it never works if I do not wrap a method into a delegate. I've tried your code, got the expected compile error, corrected and posted my version in a reply to one of the messages above.