Wednesday, 11 February 2009

Async without the pain

I've seen a number of questions lately about async operations recently, which (coupled with some notes in a book I'm proof-reading) have made me think a lot about async operations. I'll be honest: I often don't use async IO correctly, simply because it isn't friendly.

Basically, async is hard in .NET - at least to get right. One common alternative to avoid this pain is to use the synchronous version of code, but on a pool thread - i.e.

    ThreadPool.QueueUserWorkItem(delegate { /* do stuff */ });

However, while this is fine for many cases, it uses threads... we'd much prefer to use completion ports etc, which we can only really usually do by using the proper Begin/End methods that many IO wrappers provide.

So why is this a problem? Simply - you need to mess with IAsyncResult, instances, exception handling, etc. It soon gets messy. But are we missing a trick? Why can't we wrap this using functional programming?

For example, consider the following:

    static void Main() {
HttpWebRequest req = (HttpWebRequest)
WebRequest.Create("http://www.google.com/");
RunAsync<WebResponse>(
req.BeginGetResponse, req.EndGetResponse,
ProcessResponse);
Console.WriteLine("Running...");
Console.ReadLine();
}
static void ProcessResponse(WebResponse resp) {
using (StreamReader reader = new
StreamReader(resp.GetResponseStream())) {
Console.WriteLine(reader.ReadToEnd());
}
}

That doesn't look scary at all; we've hidden all the grungy details behind the opaque RunAsync method, using delegates - but we're using the proper (IO completion-based) async handlers. Here's the RunAsync method(s) - a little trickier, perhaps, but we only need to write it once - the point is that it can be used for any async Begin/End pattern (although we'd probably need to add a few overloads for common method signatures):

    static void RunAsync<T>(
Func<AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, T> end,
Action<T> callback,
Action<Exception> exceptionHandler) {
RunAsync<T>(begin, end, callback, null);
}
static void RunAsync<T>(
Func<AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, T> end,
Action<T> callback,
Action<Exception> exceptionHandler) {
begin(ar=> {
T result;
try {
result = end(ar);
} catch(Exception ex) {
if (exceptionHandler != null) {
exceptionHandler(ex);
}
return;
}
callback(result);
}, null);
}

We could probably also do something similar using a fluent API, but to be honest the above makes it simple enough for me to use...

All of which will be handy when/if I finally get around to writing an RPC client/server for protobuf-net...

-----

UPDATE: further work has shown that having two actions (result and exception) is ugly; a far more useful pattern is to take a single action; Action<Func<T>gt; (for methods with return values) or Action<Action> (for void methods). The idea is that the original caller can invoke this function to get either the value or the exception (thrown):


public static void RunAsync<T>(
Func<AsyncCallback, object, IAsyncResult> begin,
Func<IAsyncResult, T> end,
Action<Func<T>> callback) {
begin(ar => {
T result;
try {
result = end(ar); // ensure end called
callback(() => result);
} catch (Exception ex) {
callback(() => { throw ex; });
}
}, null);
}

static void ProcessResponse(Func<WebResponse> result) {
WebResponse resp = result();
using (StreamReader reader = new
StreamReader(resp.GetResponseStream())) {
Console.WriteLine(reader.ReadToEnd());
}
}

This is illustrated further here.