问题
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<>
}
回答1:
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?
回答2:
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!)
回答3:
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.
回答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).
回答5:
That clever chap Skeet approaches this subject here.
There's a different approach to "fire and forget" about half way down.
回答6:
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.
回答7:
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();
来源:https://stackoverflow.com/questions/2785194/typesafe-fire-and-forget-asynchronous-delegate-invocation-in-c-sharp