13

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<>

}
LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • Cool question. Painful code! The only thing they have in common is that they are all MulticastDelegate. I'm not sure there's going to be a typesafe way of dieting the code. How do MS generate all of it in the first place? – spender May 06 '10 at 23:30
  • 1
    @spender: The compiler automatically generates `BeginInvoke`/`EndInvoke` methods on every delegate type that are strongly typed to the arguments of the delegate. – LBushkin May 06 '10 at 23:35
  • For sure. It seems to me to encourage considerable boilerplate code, and I'm surprised that .net4 hasn't introduced a more generic (or super-generic) way of declaring these kinds of constructs... although I'd concur that in most sane code, the parameter lists wouldn't exceed 5 or so in length. – spender May 06 '10 at 23:49

7 Answers7

8

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
  • 213,145
  • 36
  • 401
  • 431
  • 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 May 06 '10 at 23:34
  • At least you can simplify your implementation of FireAndForget a bit. – dtb May 06 '10 at 23:35
  • 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 May 06 '10 at 23:37
5

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);
    }
}

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
  • 18,890
  • 14
  • 78
  • 115
  • ContinueWith(...) returns a second Task object, so you are still left with a Task that doesn't get disposed. In actual fact there is no need to dispose of the original Task object in this case as the disposal is only needed if the Task is required to perform a blocking wait. See [this question](http://stackoverflow.com/questions/3734280/) – Simon P Stevens Dec 10 '10 at 08:37
5

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
  • 120,909
  • 25
  • 266
  • 342
  • 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 May 07 '10 at 13:07
  • @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 May 07 '10 at 13:44
  • Yeah, I tried to find a reference for this too and drew a blank. – spender May 12 '10 at 23:33
4

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
  • 71,468
  • 13
  • 145
  • 180
0

Give this extension method a shot (per C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?) to ensure no memory leaks:

public static void FireAndForget( this Action action )
{
    action.BeginInvoke(action.EndInvoke, null);
}

And you could use it with generic parameters as:

T1 param1 = someValue;
T2 param2 = otherValue;
(() => myFunc<T1,T2>(param1,param2)).FireAndForget();
Community
  • 1
  • 1
Matt Klein
  • 7,856
  • 6
  • 45
  • 46
0

That clever chap Skeet approaches this subject here.

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

spender
  • 117,338
  • 33
  • 229
  • 351
  • 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 May 06 '10 at 23:47
0

You could write your own threadpool implementation. It probably sounds like wore work than it would actually be. Then you don't have to abide by "only run relatively short-lived code" advisement.

Fantius
  • 3,806
  • 5
  • 32
  • 49