views:

447

answers:

6

I recently found myself needing a typesafe "fire-and-forget" mechanism for running code asynchronously.

Ideally, what I would want to do is something like:

var myAction = (Action)(() => Console.WriteLine("yada yada"));
myAction.FireAndForget(); // async invocation

Unfortunately, the obvious choice of calling BeginInvoke() without a corresponding EndInvoke() does not work - it results in a slow resource leak (since the asyn state is held by the runtime and never released ... it's expecting an eventual call to EndInvoke(). I also can't run the code on the .NET thread pool because it may take a very long time to complete (it's advised to only run relatively short-lived code on the thread pool) - this makes it impossible to use the ThreadPool.QueueUserWorkItem().

Initially, I only needed this behavior for methods whose signature matches Action, Action<...>, or Func<...>. So I put together a set of extension methods (see listing below) that let me do this without running into the resource leak. There are overloads for each version of Action/Func.

Unfortunately, I now want to port this code to .NET 4 where the number of generic parameters on Action and Func have been increased substantially. Before I write a T4 script to generate these, I was also hoping to find a simpler more elegant way to do this. Any ideas are welcome.

public static class AsyncExt
{
    public static void FireAndForget( this Action action )
    {
        action.BeginInvoke(OnActionCompleted, action);
    }

    public static void FireAndForget<T1>( this Action<T1> action, T1 arg1 )
    {
        action.BeginInvoke(arg1, OnActionCompleted<T1>, action);
    }

    public static void FireAndForget<T1,T2>( this Action<T1,T2> action, T1 arg1, T2 arg2 )
    {
        action.BeginInvoke(arg1, arg2, OnActionCompleted<T1, T2>, action);
    }

    public static void FireAndForget<TResult>(this Func<TResult> func, TResult arg1)
    {
        func.BeginInvoke(OnFuncCompleted<TResult>, func);
    }

    public static void FireAndForget<T1,TResult>(this Func<T1, TResult> action, T1 arg1)
    {
        action.BeginInvoke(arg1, OnFuncCompleted<T1,TResult>, action);
    }

    // more overloads of FireAndForget<..>() for Action<..> and Func<..>

    private static void OnActionCompleted( IAsyncResult result )
    {
        var action = (Action)result.AsyncState;
        action.EndInvoke(result);
    }

    private static void OnActionCompleted<T1>( IAsyncResult result )
    {
        var action = (Action<T1>)result.AsyncState;
        action.EndInvoke( result );
    }

    private static void OnActionCompleted<T1,T2>(IAsyncResult result)
    {
        var action = (Action<T1,T2>)result.AsyncState;
        action.EndInvoke(result);
    }

    private static void OnFuncCompleted<TResult>( IAsyncResult result )
    {
        var func = (Func<TResult>)result.AsyncState;
        func.EndInvoke( result );
    }

    private static void OnFuncCompleted<T1,TResult>(IAsyncResult result)
    {
        var func = (Func<T1, TResult>)result.AsyncState;
        func.EndInvoke(result);
    }

    // more overloads of OnActionCompleted<> and OnFuncCompleted<>

}
+4  A: 

You can pass EndInvoke as AsyncCallback for BeginInvoke:

Action<byte[], int, int> action = // ...

action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null);

Does that help?

dtb
This was something I considered ... the concern from other developers I work with was that it would be easy to forget to pass in `action.EndInvoke` - with the result that there would be silent leaks within the code. We even looked at writing a custom FxCop rule for this, but it was taking too long and there was the risk that the code could still make it into production. I am, however, considering the merits of this option again.
LBushkin
At least you can simplify your implementation of FireAndForget a bit.
dtb
@dtb: The real version of the code actually has a little bit more logic in the OnAction/OnFunc completed methods which I didn't want to clutter into the question (primarily some logging of how long it took to complete the async call) - but, in principle, yes.
LBushkin
A: 

That clever chap Skeet approaches this subject here.

There's a different approach to "fire and forget" about half way down.

spender
Yes, I've seen that approach before. The main problem is that it loses typesafety (and hence intellisense and refactoring support) - but it does support more delegate signatures.
LBushkin
+3  A: 

The compiler-generated BeginInvoke method is also called on the thread pool (reference). So I think ThreadPool.QueueUserWorkItem would be alright, except that you're being a bit more explicit about it I guess (and I suppose a future CLR could choose to run BeginInvoke'ed methods on a different thread pool).

Dean Harding
A: 

How about something like:

public static class FireAndForgetMethods
{
    public static void FireAndForget<T>(this Action<T> act,T arg1)
    {
        var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                         TaskCreationOptions.LongRunning);
        tsk.ContinueWith(cnt => cnt.Dispose());
    }
}

Use it like:

Action<int> foo = (t) => { Thread.Sleep(t); };
foo.FireAndForget(100);

To add type safety, just expand out the helper methods. T4 is probably best here.

Scott Weinstein
+2  A: 

I notice nobody's responded to this:

I also can't run the code on the .NET thread pool because it may take a very long time to complete (it's advised to only run relatively short-lived code on the thread pool) - this makes it impossible to use the ThreadPool.QueueUserWorkItem().

I'm not sure if you're aware of this, but async delegates actually do exactly this - they queue the work on a worker thread in the ThreadPool, exactly the same as if you did QueueUserWorkItem.

The only time when async delegates behave differently is when they're special framework delegates like Stream.BeginRead or Socket.BeginSend. These use I/O completion ports instead.

Unless you're spinning of hundreds of these tasks in an ASP.NET environment, I would recommend simply using the thread pool.

ThreadPool.QueueUserWorkItem(s => action());

Or, in .NET 4, you can use the task factory:

Task.Factory.StartNew(action);

(Note that the above will also use the thread pool!)

Aaronaught
Indeed, this is a problem. I will have to re-examine the approach given this fact. Is there any clear documentation on MSDN where this is stated?
LBushkin
@LBushkin: You know, that's a very good question, and I don't think there *is* any clear documentation, it's assumed to be an implementation detail maybe. I found out for certain when I did a few experiments for somebody else's question on ASP.NET ThreadPool starvation; I stepped into an async callback and saw that it was indeed taking up a worker thread in the pool. If you find any docs, let me know, it would be nice to have something to link to.
Aaronaught
Yeah, I tried to find a reference for this too and drew a blank.
spender