October 25, 2003

.NET AsyncCallbacks and completions

I ran into a bug in my code at work the other day that turned out to be because I misunderstood something how .NET Asynchronous Delegates work.

The basic idea behind Asynchronous Delegates is that you can split a function into two calls -- BeginInvoke to start the call asynchronously, and then EndInvoke once it's done in order to clean up the state and to get the return value (if any). There's also a WaitHandle exposed through IAsyncResult (IAsyncResult.AsyncWaitHandle) that you can use to wait for the call to be completed. Async Delegates are useful obviously for any async task, but are especially useful when you start working with .NET remoting, which lets you run code remotely. Since it's going over a network link, there's a huge advantage to being able to invoke the call asynchronously without having to block on the network call.

A C# example of calling an async delegate might look like:

	m_delegate = new DoSomethingDelegate(this.DoSomething);
	m_callback = new AsyncCallback(this.CompletionProc);
...
	IAsyncResult iar = m_delegate.BeginInvoke(m_callback, null);
	iar.AsyncWaitHandle.WaitOne();

My mistake was in thinking that the AsyncWaitHandle would be set (i.e., the call to WaitOne would finish) once the async callback (m_callback) had finished. But this isn't true: it is set as soon as the async call (the call through m_delegate) finishes.

In other words, once the async call finishes, the framework sets the AsyncWaitHandle and starts an async call to the AsyncCallback completion proc. If you really need to block until the AsyncCallback function finishes, you'll need to use something other than IAsyncResult.

You can download some C# sample code to see this in action. DoSomething is the async delegate, which sleeps for 1000 ms and returns. The completion proc also sleeps for 1000 ms and returns. When you run it, you'll see something like this:

About to call DoSomething at 0 ms
Enter DoSomething at 40 ms
Exit DoSomething at 1041 ms
WaitOne finished at 1041 ms
Enter CompletionProc at 1041 ms
Exit CompletionProc at 2042 ms

You can clearly see that IAsyncResult.AsyncWaitHandle was set ("WaitOne finished") at the same time as the async callback was called -- not when the async callback finished.

As a final note, you might wonder why there's a 40 ms delay between calling DoSomething asynchronously and having it actually start. This is true only the first time you use that asynchronous delegate -- subsequent calls have virtually no delay.

Posted by Mike at October 25, 2003 10:54 AM