Wednesday, March 18, 2009

The .NET Async pattern is a very neat way of running functionality asynchronously. The async pattern is where for a synchronous method DoWork be have a pair of methods BeginDoWork and EndDoWork to handle running the DoWork functionality on the threadpool. Now it is well documented that if I call the Begin method I must also call the End method to allow the async infrastructure to clean up resources it may have acquired. However, where do I call the End version?

Consider async delegate invocation. Quite often I’d like to just fire off the async method and forget about it but I have to call EndInvoke. Fortunately lambdas make this really easy

Action a = DoWork;
a.BeginInvoke( ar => a.EndInvoke(ar) );

Now there is a nasty problem. What happens if DoWork throws an exception? The async delegate infrastructure will conveniently cache the exception and re-throw it when I call EndInvoke. However, the issue is that this is happening in an AsyncCallback delegate on a threadpool thread and so I cannot catch it easily in this construct. Why is that a problem? well since .NET 2.0 an unhandled exception on a background thread will terminate the process. This means to reliably call EndInvoke in an AsyncCallback we must put it in a try … catch. This is annoying code to reliably put in place and is easily forgotten. So I have written an extension method to wrap this functionality for you

public static class Extensions
{
 
public static AsyncCallback Try(this AsyncCallback cb, Action<Exception> exceptionAction)
 
{
   
AsyncCallback wrapper = delegate(IAsyncResult iar)
    {
     
try
     
{
       
cb(iar);
     
}
     
catch (Exception e)
     
{
       
exceptionAction(e);
     
}
   
};
   
return wrapper;
  }
}

So this code extends AsyncCallback and takes a delegate to call if an exception takes place it then wraps its own AsyncCallback around the one passed in this time putting it in a try … catch block. The usage looks like this:

Action a = DoStuff;
AsyncCallback cb = ia => a.EndInvoke(ia);
a.BeginInvoke(cb.Try(e => Console.WriteLine(e)), null);

The only awkward thing here is having to take the lambda out of the call to BeginInvoke because the C# compiler won’t allow the dot operator on a lambda (without casting it to an AsyncCallback) but at least this wraps up some of the issues

Wednesday, March 18, 2009 4:37:08 PM (GMT Standard Time, UTC+00:00)  #    Comments [3]Trackback
Thursday, April 09, 2009 8:48:50 AM (GMT Daylight Time, UTC+01:00)
Hi Richard,

This is an excellent idea, but the slightly awkward syntax you mention in your last paragraph got me thinking - with the following extension method for Action (rather than AsyncCallback), you can achieve a much cleaner calling syntax for the 'fire and forget' type of asynchronous invocation, while still being able to handle exceptions as in your example.

So with the AsyncInvoke extension method as defined below, the calling code looks like this:

Action a = DoStuff;
action.AsyncInvoke(e => Console.WriteLine(e));


Do you have any thoughts on this approach?




static class InvokeExtensions
{
public static void AsyncInvoke(this Action action, Action<Exception> exceptionAction)
{
action.BeginInvoke(
delegate(IAsyncResult iar)
{
try
{
action.EndInvoke(iar);
}
catch(Exception e)
{
exceptionAction(e);
}
}
, null);
}
}
Alex Goodstein
Wednesday, April 15, 2009 9:03:45 PM (GMT Daylight Time, UTC+01:00)
Hey - is that *the* Alex Goodstein from our Securicor days?

Thats certainly a neater syntax - although only dowks for delegates of type Action which could be very limiting if it wasn't for the availability of anonymous methods but with anon methods you can wrap any method in a code block that you assign to an Action delegate and spawn the Action asynchonously

Action a = delegate
{
DoStuff( 42, "Hello", myLocalVariable );
};

However, the one place this won't work is when you have a return value that you want to pick up - but then that wasn't what my article was about anyway - nice one.
Richard
Friday, February 05, 2010 8:23:19 AM (GMT Standard Time, UTC+00:00)
If you are really not interested in knowing the result then the async pattern is probably not what you should be in using. Would it not just be better to use ThreadPool.QueueUserWorkItem
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):