C#中的Typesafe即发即弃异步委托调用

我最近发现自己需要一种类型安全的“即发即忘”机制来异步运行代码。

理想情况下,我想要做的是:

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

不幸的是,在没有相应的EndInvoke() BeginInvoke()情况下调用BeginInvoke()的明显选择不起作用 – 它导致资源泄漏缓慢(因为asyn状态由运行时保持并且从未释放…它期望最终调用EndInvoke() 。我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程池上运行相对短暂的代码) – 这使得它无法使用ThreadPool.QueueUserWorkItem()

最初,我只对签名与ActionActionFunc匹配的方法只需要此行为。 所以我把一组扩展方法(见下面的清单)放在一起,让我这样做而不会遇到资源泄漏。 每个版本的Action / Func都有重载。

不幸的是,我现在想将此代码移植到.NET 4,其中Action和Func上的generics参数的数量已大幅增加。 在我编写T4脚本来生成这些之前,我还希望找到一种更简单,更优雅的方法来实现这一点。 欢迎任何想法。

 public static class AsyncExt { public static void FireAndForget( this Action action ) { action.BeginInvoke(OnActionCompleted, action); } public static void FireAndForget( this Action action, T1 arg1 ) { action.BeginInvoke(arg1, OnActionCompleted, action); } public static void FireAndForget( this Action action, T1 arg1, T2 arg2 ) { action.BeginInvoke(arg1, arg2, OnActionCompleted, action); } public static void FireAndForget(this Func func, TResult arg1) { func.BeginInvoke(OnFuncCompleted, func); } public static void FireAndForget(this Func action, T1 arg1) { action.BeginInvoke(arg1, OnFuncCompleted, 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( IAsyncResult result ) { var action = (Action)result.AsyncState; action.EndInvoke( result ); } private static void OnActionCompleted(IAsyncResult result) { var action = (Action)result.AsyncState; action.EndInvoke(result); } private static void OnFuncCompleted( IAsyncResult result ) { var func = (Func)result.AsyncState; func.EndInvoke( result ); } private static void OnFuncCompleted(IAsyncResult result) { var func = (Func)result.AsyncState; func.EndInvoke(result); } // more overloads of OnActionCompleted and OnFuncCompleted } 

您可以将EndInvoke作为AsyncCallback传递给BeginInvoke:

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

这有帮助吗?

我注意到没有人对此作出回应:

我也无法在.NET线程池上运行代码,因为它可能需要很长时间才能完成(建议只在线程池上运行相对短暂的代码) – 这使得无法使用ThreadPool。 QueueUserWorkItem()。

我不确定你是否意识到这一点,但是异步委托实际上就是这样做的 – 他们将工作排在ThreadPool的工作线程上,就像你使用QueueUserWorkItem

异步委托表现不同的唯一时间是它们是特殊的框架委托,如Stream.BeginReadSocket.BeginSend 。 这些使用I / O完成端口。

除非你在ASP.NET环境中使用数百个这样的任务,否则我建议只使用线程池。

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

或者,在.NET 4中,您可以使用任务工厂:

 Task.Factory.StartNew(action); 

(注意以上也会使用线程池!)

怎么样的:

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

使用它像:

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

要添加类型安全性,只需展开辅助方法即可。 T4在这里可能是最好的。

编译器生成的BeginInvoke方法也在线程池( 引用 )上调用。 所以我认为ThreadPool.QueueUserWorkItem会没问题,除非你对它有点明确(我猜想未来的CLR可以选择在不同的线程池上运行BeginInvoke的ed方法)。

那个聪明的小伙子Skeet 在这里接近这个主题。

在一半的时间里,有一种不同的方法可以“发射并忘记”。

您可以编写自己的线程池实现。 这可能听起来像是工作而不是实际。 然后你不必遵守“只运行相对短命的代码”的建议。

给这个扩展方法一个机会(每个C#是action.BeginInvoke(action.EndInvoke,null)一个好主意? )以确保没有内存泄漏:

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

您可以将它与通用参数一起使用:

 T1 param1 = someValue; T2 param2 = otherValue; (() => myFunc(param1,param2)).FireAndForget();