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.

11 comments:

Marc Gravell said...

btw - yes, I know about WebClient and the Download...Completed events - this entry is more about the general pattern than the specific WebRequest example.

Marc Gravell said...

Also - I know we could also use BeginRead etc on the Stream - I have code that does this (it isn't much different), but I don't think it adds much to the example. Available on request ;-p

Konstantin Savelev said...

Sorry, didn't catch why you call callback(T) even if exception was thrown?

I would prefer:

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

Marc Gravell said...

It doesn't run "callback" on error - see the "return" in the catch block.

The exception handling was quite deliberate, so that the "exceptionHandler" would only be used for exceptions in the async method (i.e. reported by end(...)) - the callback can always do its own handling. But yes, there are several different variants we could use here.

Konstantin Savelev said...

You are right. Sorry

Marc Gravell said...

As an update... it is much tidier if instead of 2 actions (T and exception), we accept:

Action<Func<T>> ...

When the action is raised, the caller invokes the func; this *either* returns the T *or* throws the exception. Tidy!

pete.d said...

I am intrigued by the idea that the existing IAsyncResult pattern is too difficult (?), and yet in your update, you propose a potentially confusing pattern as a way of making the simplification "tidier".

I appreciate the elegance of passing a Func〈T〉 to the user's Action delegate. It's a nice use of the functional aspects found in C#, and I think it does a decent job of expressing the implementation.

But, it adds an extra layer of delegate calling, and also takes the solution back towards the normal IAsyncResult pattern (i.e. the callback has to call a method to get the actual results).

What have we really gained here? Admittedly, I don't find the IAsyncResult pattern all that difficult to start with; maybe that makes me unqualified to judge things intended to simplify it. But, the basic idea of the IAsyncResult pattern is: call a method, providing a callback in the process; when your callback is called, call another method to complete the operation.

You've factored out a small portion of that work -- specifically, some of the casting involved to retrieve important data -- but otherwise it seems like the "RunAsync" method here is practically the same.

What am I missing?

Marc Gravell said...

My comment about IAsyncResult being too difficult is based on the amount of times I see people struggling with it.

Exposing a Func<T> is the cleanest way I can see if encapsulating the exception handling and the value.

Re "what have we gained"; the fact is IAsyncResult, AsyncResult and AsyncCallback have nothing at all to do with the original problem; RunAsync expresses intent using only the callers original logic: "I have a function that returns Foo; call me when the result is ready".

Using the Func<T> approach also allows us to complete the necessary End... call (very necessary) - i.e. it doesn't matter whether the caller invokes the function we give them; we're not going to leak anything (contrast to the number of times people incorrectly use Begin... without End..., to get a "fire and forget" message).

Marc Gravell said...

As an aside - I've recently found that the above is strikingly similar to the approach used under the bonnet in F#, by Async.BuildPrimitive; not identical, but very close.

Noah said...

This is pretty cool. I wrote a little follow-up to this (and a response posted on monstersgotmy.net) at http://statichippo.com/archive/2009/11/09/run-generic-tasks-async-fluent-ly.aspx. I use ThreadPoo.QueueUserWorkItem and also added the ability to block until all calls complete.

Chen Hendrawan said...

What do you suggest for a Begin/End method that has many overloads? For example BeginReceive in System.Net.Sockets.Socket has 4 overloads.

This is just for BeginReceive method. Other methods has different signature. This would means a lot of RunAsync :(