This entry presents an easy short-cut to implementing the Event-based Asynchronous Pattern in your classes, using the functional programming features of C# 3.0.
I'm currently building a framework to support a complex smart client application, and I'm trying to make good practices as easy to use as possible for the developers who use it. One facet of this is making multi-threading accessible, and the best way I've seen to do that is the Event-Based Asynchronous Pattern that was introduced in .NET 3.0.
In its simplest form, the pattern consists of a method and an event. The method executes on a background thread, and the event is raised when the method completes or throws an exception. The base class library provides an AsyncCompletedEventArgs type which includes an Error property to hold any exception thrown during the execution. For methods which return a value, that value is provided within the EventArgs; that's the pattern I'm going to cover here.
This simple form is another ideal candidate for a shortcut using delegates. A basic implementation might look like this:
public void GetUriAsync(Uri uri)
{
Action asyncAction = () =>
{
try
{
string text = GetUri(uri);
if (GetUriCompleted != null)
{
GetUriCompleted(this, new GetUriCompletedEventArgs(text));
}
}
catch (Exception ex)
{
if (GetUriCompleted != null)
{
GetUriCompleted(this, new GetUriCompletedEventArgs(ex));
}
}
};
AsyncCallback callback = (asyncResult) => asyncAction.EndInvoke(asyncResult);
asyncAction.BeginInvoke(callback, null);
}
public event GetUriCompletedEventHandler GetUriCompleted;
This code creates an Action delegate and uses its BeginInvoke method as a simple way to achieve the background threading. There is also a custom class, GetUriCompletedEventArgs, (not shown) which derives from AsyncCompletedEventArgs and provides a property for the text returned from the call, and a custom delegate for the event handler.
Casting a short-cutting eye over the code, it's fairly easy to see that the distinguishing parts are just the method being called, the type it returns, and the event handler; everything else is boilerplate. And methods, types and event handlers can all be passed as parameters.
Let's deal with the return type and the event handler first. Rather than declare a new EventArgs class and EventHandler delegate every time we implement this pattern, we can just create a single generic class and delegate which can be re-used every time:
public class AsyncCompletedEventArgs<T> : AsyncCompletedEventArgs
{
private readonly T returnValue;
public AsyncCompletedEventArgs(T returnValue, object userState)
: base(null, false, userState)
{
this.returnValue = returnValue;
}
public AsyncCompletedEventArgs(Exception error, object userState)
: base(error, false, userState) { }
public T ReturnValue
{
get
{
this.RaiseExceptionIfNecessary(); // Inherited from AsyncCompletedEventArgs
return returnValue;
}
}
}
public delegate void AsyncCompletedEventHandler<T>(object sender, AsyncCompletedEventArgs<T> e);
(Notice that the call to the base constructor passes false for the cancelled parameter. It's not really possible to implement a cancellable background process using this technique, as you would have to include cancellation awareness into the implementation code in the actual GetUri method.)
Now we can create a single helper method which will take any Func<T> delegate and execute it asynchronously:
public static void InvokeAsync<T>(Func<T> func, object sender,
AsyncCompletedEventHandler<T> eventHandler, object userState)
{
// Wrap the passed Func in an Action delegate which raises the AsyncComplete event
Action asyncAction = () =>
{
try
{
// Call the supplied Func
T returnValue = func();
// Raise the AsyncCompleted event with the returned value
eventHandler(sender, new AsyncCompletedEventArgs<T>(returnValue, userState));
}
catch (Exception ex)
{
// Raise the AsyncCompleted event with the exception
eventHandler(sender, new AsyncCompletedEventArgs<T>(ex, userState));
}
};
// Use a callback to call EndInvoke on the Action delegate
AsyncCallback callback = (asyncResult) => asyncAction.EndInvoke(asyncResult);
// Start the Action delegate on another thread using BeginInvoke
asyncAction.BeginInvoke(callback, null);
}
This is the same technique as the custom-written GetUriAsync method above, but it takes a Func<T> delegate, which could do pretty much anything, and some extra parameters for the event handling process. The userState value is intended to identify which call to a method is raising the event in situations where multiple calls are made.
In this code, all the parameters are turned into closures within the asyncAction delegate, and the asyncAction delegate is itself passed as a closure into the callback delegate.
Consuming this helper looks like this:
public class Example
{
public string GetUri(Uri uri)
{
...
}
public void GetUriAsync(Uri uri)
{
AsyncHelper.InvokeAsync<string>(() => GetUri(uri), this, GetUriCompleted, uri);
}
public event AsyncCompletedEventHandler<string> GetUriCompleted;
}
A single line of code and an event are all that's now required to provide the pattern functionality. The call to GetUri(uri) is wrapped in a parameterless Func<T> delegate; this would work unmodified with methods with multiple parameters. We pass the Example object to be the sender of the event, the event handler, and use the uri object as the userState value so the caller can identify which method is "returning".
The other big advantage to using techniques like this is that in the future, you can change the implementation code in the helper method, maybe to use something in the .NET 4.0 Parallel Extensions, and all your consumer classes pick up the new implementation. And unlike C++ macros, you don't even need to rebuild anything except the Helper assembly.
I've included all this code, along with an alternative AsyncInvoke method which takes an Action delegate instead of a Func<T>, in the FunctionalHelpers solution on my MSDN Code Gallery page. From now on, I'll add any new code to this solution, rather than uploading lots of little projects.