Asbestos Supply

2009-11-09 Run generic tasks async (fluent-ly)

I saw a post entitled Run Async tasks with Fluent Interface written in response to another post entitled Async without the pain.  I suppose this post is my response to those posts. 

While I liked what I saw there, there were some things I wanted to add (like the ability to block until tasks complete), so I wrote my own class along the same lines.  There are still some things I'd like to add (like the ability to queue tasks into jobs that run in order), but this works for me right now for what I'm doing.

public class AsyncQueueManager
{
    private ILog _logger = log4net.LogManager.GetLogger(typeof(AsyncQueueManager));
    private ManualResetEvent _WaitHandle = new ManualResetEvent(true);
    private int _QueueCount = 0;
    object locker = new object();

    private Action<object> GetActionWrapper(Action<object> action)
    {
        Action<object> actionWrapper = (object obj) =>
                    {
                        try
                        {
                            action(obj);
                        }
                        catch(Exception ex)
                        {
                            LogException(action, ex);
                        }
                        finally
                        {
                            lock (locker)
                                _QueueCount = _QueueCount - 1;
                            SetWaitHandle();
                        }
                    };
        return actionWrapper;
    }
    public AsyncQueueManager Queue(Action<object> action)
    {
        lock(locker)
            _QueueCount = _QueueCount + 1;
        _WaitHandle.Reset();            
        Action<object> actionWrapper = GetActionWrapper(action);
        WaitCallback waitCallback = new WaitCallback(actionWrapper);
        ThreadPool.QueueUserWorkItem(waitCallback);
        return this;
    }
    private void SetWaitHandle()
    {
        if (_QueueCount == 0)
        {
            _WaitHandle.Set();
        }
    }

    public void WaitOne()
    {
        _WaitHandle.WaitOne();
    }

    public void WaitOne(TimeSpan timeout)
    {
        _WaitHandle.WaitOne(timeout);
    }

    private void LogException(Action<object> action, Exception ex)
    {
        string exceptionString = "Exception occured while running async operation." + 
            "  Details below." + Environment.NewLine +
            "Method name:" + action.Method.Name + Environment.NewLine +
            "Exception:" + ex.ToString();
        _logger.Error(exceptionString);
    }
}

Running code is as simple as:

int resultA;
int resultB;
int resultC;
var async = new AsyncQueueManager();
async.Queue((object obj) => {
    int result = 0;
    for (int i = 0; i < 1000000; i++)
        result++;
    resultA = result;
});
async.Queue((object obj) => {
    int result = 1;
    for (int i = 0; i < 1000000; i++)
        result += 2;
    resultB = result;
});
async.Queue((object obj) => {
    int result = 1;
    for (int i = 0; i < 1000000; i++)
        result += 3;
    resultC = result;
});
async.Wait(); // wait for these to complete in no particular order

Console.WriteLine("resultA: " + resultA);
Console.WriteLine("resultB: " + resultB);
Console.WriteLine("resultC: " + resultC);

Now you'd have to have a pretty slow machine for this to actually take any noticeable time, but you get the idea.

Notice of course how I can set local variables within my in-line Actions.  This is because, as I understand it, the compiler actually creates a separate class to handle these since it knows where you're going with this.  Of course you could also call methods or do whatever you want here.