Monday, October 12, 2009

Taming the APM pattern with AsyncAction/AsyncFunc

Ok, so in the previous post I talked about how nesting asynchronous operations using the APM  pattern quickly turns into a world of pain if the operation is implemented on a class that’s new’d up per call in your method. But I’ll recap for the sake of clarity.

Easy Case

Implementation of the async operation is ‘on’ your instance, either directly or through composition:

    public IAsyncResult BeginDoSomething(int input, AsyncCallback callback, object state)

    {

        return _command.BeginInvoke(input, callback, state);               

    }

 

    public string EndDoSomething(IAsyncResult result)

    {

        return _command.EndInvoke(result);

    }

And you are done! Nothing more to see here. Keep moving.

Hard Case

Implementation of the async operation is on something you ‘new-up’ for the operation, like a SQL command or somesuch:

    public IAsyncResult BeginDoSomething(int input, AsyncCallback callback, object state)

    {

        var command = new SomeCommand();

        return command.BeginDoSomethingInternal(input, callback, state);

    }

 

    public string EndDoSomething(IAsyncResult result)

    {

        // we are screwed, since we don't have a reference to 'command' any more

        throw new HorribleException("Argh");

    }

Note the comment in the EndDoSomething method. Also note that most of the ‘easy’ ways to get around this either break the caller, fail if callback / state are passed as null / are non-unique, introduce race conditions or don’t properly support all of the ways you can complete the async operation (see previous post for more more details).

Pyrrhic Fix

I got it working using using an AsyncWrapper class and a bunch of state-hiding-in-closures. But man it looks like hard work:

    public IAsyncResult BeginDoSomething(int value, AsyncCallback callback, object state)

    {

        var command = new SomeCommand();

        AsyncCallback wrappedCallback = null;

 

        if (callback != null)

            wrappedCallback = delegate(IAsyncResult result1)

                                  {

                                      var wrappedResult1 = new AsyncResultWrapper(result1, state);

                                      callback(wrappedResult1);

                                  };

 

        var result = command.BeginDoSomethingInternal(value, wrappedCallback, command);

        return new AsyncResultWrapper(result, state);

    }

 

    public string EndDoWork(IAsyncResult result)

    {

        var state = (AsyncResultWrapper)result;

        var command = (SomeCommand)state.InnerResult.AsyncState;

        return command.EndDoSomething(state.InnerResult);

    }

Just thinking about cut-and-pasting that into all my classes proxies makes my head hurt. There must be a better way. But there wasn’t. So…

AsyncAction<T> / AsyncFunc<T,Rex>

What I wanted was some way to wrap all this up, and provide an ‘easy’ API to implement this (‘cuz we got lots of them to do). Here’s what the caller now sees:

    public IAsyncResult BeginDoSomething(int input, AsyncCallback callback, object state)

    {

        var command = new SomeCommand();

        var asyncFunc = new AsyncFunc<int, string>(

            command.BeginDoSomethingInternal,

            command.EndDoSomething,

            callback, state);

        return asyncFunc.Begin();

    }

 

    public string EndDoSomething(IAsyncResult result)

    {

        var asyncFuncState = (IAsyncFuncState<string>)result;

        return asyncFuncState.End();

    }

I think that’s a pretty major improvement personally. For brevity I won’t post all the implementation code here, but from the AsyncWrapper implementation above and the signature you can pretty much bang it together in a few mins. It’s surprisingly easy when you know what you are aiming for.

Curried Lama

Now actually in my usage I needed something a bit more flash, where the instance of the object that the Begin method was to execute on would be determined ‘late on’, rather than frozen into the constructor. So I ended up with something looking a bit more like this:

    var asyncFunc = new AsyncFunc<SomeCommand, int, string>(

        c => c.BeginDoSomethingInternal,

        c => c.EndDoSomethingInternal,

        callback, state);

 

 

    // Later on...

    var command = new SomeCommand();

    return asyncFunc.Begin(command);

 

(The idea is that a series of these can get stored in a chain, and executed one-by-one, with ‘command’ actually replaced by a state machine – i.e. each operation gets to execute against the current state object in the state machine at the that operation executes)

This results in some crazy signatures in the actual AsyncFunc class, the kind that keep Mitch awake at night muttering about the decay of modern computer science:

    public AsyncFunc(

        Func<TInstance, Func<TInput, AsyncCallback, object, IAsyncResult>> beginInvoke,

        Func<TInstance, Func<IAsyncResult, TReturn>> endInvoke,

        AsyncCallback callback,

        object state

    )

…but it was that or:

    var asyncFunc = new AsyncFunc<SomeCommand, int, string>(

        (c,a,cb,s) => c.BeginDoSomethingInternal(a,cb,s),

        (c,ar) => c.EndDoSomethingInternal(ar),

        callback, state);

…which is just more fiddly typing for the user, not the implementer. And it made for some funky currying for overloaded versions of the ctor where you wanted to pass in a ‘flat’ lambda:

    _beginInvoke = (i) => (a,c,s) => beginInvoke(i,a,c,s);

Best keep quiet about that I think.

Since this is the async equivalent of Func<TArg,TRet>, you are probably wondering about async version of Action<TArg> and yes there is one of those. And of course, if you want more than one argument (Action<TArg1,TArg2>) you will need yet another implementation. That really sucks, but I can’t see us getting generic-generics any time soon.

I was talking to Joe Albahari about this at TechEd and he suggested that I was attempting to implement Continuations, which wasn’t how I’d been thinking about it but is about right. But I’d need to post about calling a series of these using AsyncActionQueue<TInstance, TArg> to really explain that one.

 

PS: As it turns out, this looks a fair bit like some of the Task.FromAsync factory methods in .Net 4:

public Task<TResult> FromAsync<TArg1, TResult>(
Func<TArg1, AsyncCallback, Object, IAsyncResult> beginMethod,
Func<IAsyncResult, TResult> endMethod,
TArg1 arg1,
Object state
)















…but I didn’t know that at the time. Yeah, you can chain those too. Grr. It’s just yet another example of why .Net 4 should have come out a year ago so I could be using it on my current project. And don’t even mention covariance to me.



PPS: Have I totally lost it this time?

2 comments:

Jack Greenfield said...

Nice pair of posts. Helps with Silverlight programming, where one is forced to call async methods on objects constructed in one's own method, if one wants to wrap them.

piers7 said...

Absolutely. Any time you have to wrap an async operation on an object with 'per call' lifetime semantics, you have to deal with this pain.
Roll on .net 4

Popular Posts