我最近发现自己需要一种类型安全的“即发即忘”机制来异步运行代码。
理想情况下,我想要做的是:
var myAction = (Action)(() => Console.WriteLine("yada yada"));
myAction.FireAndForget(); // async invocation
不幸的是,在没有相应的BeginInvoke()
的情况下调用EndInvoke()
的明显选择不起作用 - 它导致资源泄漏缓慢(因为asyn状态由运行时保持并且从未发布...它期望最终调用EndInvoke()
。我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程上运行相对短暂的代码池) - 这使得无法使用ThreadPool.QueueUserWorkItem()
。
最初,我只对签名与Action
,Action<...>
或Func<...>
匹配的方法才需要此行为。所以我把一组扩展方法(见下面的清单)放在一起,让我在不遇到资源泄漏的情况下这样做。每个版本的Action / Func都有重载。
不幸的是,我现在想将此代码移植到.NET 4,其中Action和Func上的泛型参数的数量已大幅增加。在我编写T4脚本来生成这些之前,我还希望找到一种更简单,更优雅的方法来实现这一点。欢迎任何想法。
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<>
}
答案 0 :(得分:8)
您可以将EndInvoke作为AsyncCallback传递给BeginInvoke:
Action<byte[], int, int> action = // ...
action.BeginInvoke(buffer, 0, buffer.Length, action.EndInvoke, null);
这有帮助吗?
答案 1 :(得分:5)
如下:
public static class FireAndForgetMethods
{
public static void FireAndForget<T>(this Action<T> act,T arg1)
{
var tsk = Task.Factory.StartNew( ()=> act(arg1),
TaskCreationOptions.LongRunning);
}
}
使用它像:
Action<int> foo = (t) => { Thread.Sleep(t); };
foo.FireAndForget(100);
要添加类型安全性,只需展开辅助方法即可。 T4在这里可能是最好的。
答案 2 :(得分:5)
我注意到没有人对此作出回应:
我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程池上运行相对短暂的代码) - 这使得它无法使用ThreadPool.QueueUserWorkItem()。
我不确定你是否意识到这一点,但是异步委托实际上正是这样做的 - 他们将工作排在ThreadPool
的工作线程上,与你{{1}完全相同}}
异步委托表现不同的唯一情况是,它们是QueueUserWorkItem
或Stream.BeginRead
等特殊框架代理。这些使用I / O完成端口。
除非你在ASP.NET环境中使用数百个这样的任务,否则我建议你只使用线程池。
Socket.BeginSend
或者,在.NET 4中,您可以使用任务工厂:
ThreadPool.QueueUserWorkItem(s => action());
(注意以上也会使用线程池!)
答案 3 :(得分:4)
还在线程池(reference)上调用编译器生成的BeginInvoke
方法。所以我认为ThreadPool.QueueUserWorkItem会没问题,除非你对它有点明确(我猜想未来的CLR可以选择在不同<上运行BeginInvoke
'ed方法/ em>线程池)。
答案 4 :(得分:0)
聪明的小姐Skeet接近这个主题here。
有一种不同的方法可以“解雇”,大约一半。
答案 5 :(得分:0)
您可以编写自己的线程池实现。这可能听起来像是工作而不是实际。那么你就不必遵守“只运行相对短命的代码”的建议。
答案 6 :(得分:0)
为此扩展方法提供一个镜头(每C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?)以确保没有内存泄漏:
public static void FireAndForget( this Action action )
{
action.BeginInvoke(action.EndInvoke, null);
}
您可以将它与通用参数一起使用:
T1 param1 = someValue;
T2 param2 = otherValue;
(() => myFunc<T1,T2>(param1,param2)).FireAndForget();