Monday, September 1, 2008

Asynchronous Method Invocation

I'm currently in the process of developing a plug-in framework that enables business developers to create individual modules/plug-in's to be hosted in a management console.

It soon became an issue that we would have to support some kind of multithreading features to avoid blocking the UI.

IMHO, the UI thread (main thread) should have one task and that is making the  application look alive to the end user.

We looked at several options from running each plug-in in its own app domain to loading forms on different threads. Nothing really ended up like we wanted to. Too much code, too much setup. There had to be a better way and it had to be easy to use.

The Background Worker

Our first thought was that the business developers could use the Background Worker to do their asynchronous stuff.

While this certainly solves the problem we still thought it would be good if we did not need to set up event handlers, pass arguments as objects and so on.

The good thing about it though, is that it takes care of invoking the  RunWorkerCompleted event on the appropriate thread. This way you don't run into cross-thread exceptions when accessing a UI component.

The IAsyncInvocator interface

/// <summary>
/// Represents a class that is capable of invoking a method asynchronously. 
/// </summary>
public interface IAsyncInvocator
{
    /// <summary>
    /// Executes the function as described in the <paramref name="func"/> delegate.
    /// </summary>
    /// <typeparam name="TTarget">The type of target that will be subject for invocation.</typeparam>
    /// <typeparam name="TResult">The type of result from the method invocation.</typeparam>
    /// <param name="target">An instance of <paramref name="{TTarget}"/> </param>
    /// <param name="func">Represents the method to be invoked asynchronously.</param>
    /// <param name="callBack">Represents the method to be called after method has been executed.</param>
    void AsyncInvoke<TTarget, TResult>(TTarget target, Func<TTarget, TResult> func, Action<TResult> callBack);
}

As mentioned we needed a simple API to let our class library users invoke they asynchronous methods.
For those of you not familiar with the Func<T> and Action<T> delegates, this interface may look a little complicated.
Lets put it to use and discover that is not all that complicated.

invocator.AsyncInvoke(this, m => m.SomeMethod(),r => Console.WriteLine("Result: {0}" ,r));

That's it. The method "SomeMethod" is invoked asynchronously and the result is passed to the callback that in our little example just prints out the result to the console.

Oh, I almost forgot

It's kind of funny actually, following the TDD (Test Driven Development) pattern, I often find myself forgetting about this last tiny detail:)

Here is the code implementing the interface

[Implements(typeof(IAsyncInvocator),LifecycleType.OncePerRequest)]
public class AsyncInvocator : IAsyncInvocator
{               
 
    #region IAsyncInvocator Members

    /// <summary>
    /// Executes the function as described in the <paramref name="func"/> delegate.
    /// </summary>
    /// <typeparam name="TTarget">The type of target that will be subject for invocation.</typeparam>
    /// <typeparam name="TResult">The type of result from the method invocation.</typeparam>
    /// <param name="target">An instance of <paramref name="{TTarget}"/> </param>
    /// <param name="func">Represents the method to be invoked asynchronously.</param>
    /// <param name="callBack">Represents the method to be called after method has been executed.</param>
    public void AsyncInvoke<TTarget, TResult>(TTarget target, Func<TTarget, TResult> func, Action<TResult> callBack)
    {           
        AsyncOperation asyncOperation = AsyncOperationManager.CreateOperation(null);
        ThreadPool.QueueUserWorkItem(state =>
                                         {
                                             TResult result = func(target);
                                             asyncOperation.Post(argument => callBack(result), null);
                                             asyncOperation.OperationCompleted();
                                         });
    }

    #endregion
}

As you may notice, the AsyncOperationManager comes in very handy in this scenario. In short it takes care of executing the callback delegate on the calling thread.
Actually this is pretty much the same way the BackGroundWorker handles the RunWorkerCompleted event.
The observant reader may have noticed the Implements attribute at the class level. It is just a configuration attribute so that my favoritte IOC container can gain awareness over this type.

TDD and IOC(Inversion of Control) goes hand in hand and I strongly recommend that you check out the LinFu framework for a container that actually is understandable. It provides a very simple API and has a 100 percent test coverage. The project owner's name is Philip Laureano and he is among, if not the most inspiring individual I have met in my 15 years as a developer.  My thanks goes out to you, my friend.

No comments: