在asp.net进程中运行多个并行任务的正确方法是什么?
我想我不理解什么。 我原以为Task.Yield()
强制为一个任务启动一个新的线程/上下文,但是在重新阅读这个答案时,它似乎只是强制该方法是异步的。 它仍然是在相同的背景下。
什么是正确的方法 – 在asp.net进程中 – 并行创建和运行多个任务而不会导致死锁?
换句话说,假设我有以下方法:
async Task createFileFromLongRunningComputation(int input) { //many levels of async code }
当某个POST路由被命中时,我想同时启动上述方法3次, 立即返回 ,但是当完成所有这三个时都要记录。
我想我需要在我的行动中加入这样的东西
public IHttpAction Post() { Task.WhenAll( createFileFromLongRunningComputation(1), createFileFromLongRunningComputation(2), createFileFromLongRunningComputation(3) ).ContinueWith((Task t) => logger.Log("Computation completed") ).ConfigureAwait(false); return Ok();
}
什么需要进入createFileFromLongRunningComputation
? 我原以为Task.Yield
是正确的,但显然不是。
将并发工作卸载到不同线程的正确方法是使用Task.Run
作为rossipedia建议。
ASP.Net中的后台处理的最佳解决方案(您的AppDomain
可以与所有任务一起自动回收/关闭)在Scott Hanselman和Stephen Cleary的博客中(例如HangFire)
但是,您可以将Task.Yield
与ConfigureAwait(false)
一起使用来实现相同的function。
所有Task.Yield
都返回一个awaiter,确保方法的其余部分不会同步进行(通过让IsCompleted
返回false
并且OnCompleted
立即执行Action
参数)。 ConfigureAwait(false)
忽略SynchronizationContext
,因此强制该方法的其余部分在ThreadPool
线程上执行。
如果你同时使用它们,你可以确保async
方法立即返回一个将在ThreadPool
线程上执行的任务(如Task.Run
):
async Task CreateFileFromLongRunningComputation(int input) { await Task.Yield().ConfigureAwait(false); // executed on a ThreadPool thread }
编辑:George Mauer指出,由于Task.Yield
返回YieldAwaitable
您无法使用YieldAwaitable
ConfigureAwait(false)
这是Task
类的方法。
你可以通过使用具有非常短的超时的Task.Delay
来实现类似的东西,所以它不会是同步的,但你不会浪费太多时间:
async Task CreateFileFromLongRunningComputation(int input) { await Task.Delay(1).ConfigureAwait(false); // executed on a ThreadPool thread }
一个更好的选择是创建一个YieldAwaitable
,它只是忽略SynchronizationContext
,就像使用ConfigureAwait(false)
一样:
async Task CreateFileFromLongRunningComputation(int input) { await new NoContextYieldAwaitable(); // executed on a ThreadPool thread } public struct NoContextYieldAwaitable { public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); } public struct NoContextYieldAwaiter : INotifyCompletion { public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { var scheduler = TaskScheduler.Current; if (scheduler == TaskScheduler.Default) { ThreadPool.QueueUserWorkItem(RunAction, continuation); } else { Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler); } } public void GetResult() { } private static void RunAction(object state) { ((Action)state)(); } } }
这不是推荐,它是对Task.Yield问题的回答。
( l3arnon的答案是正确答案。这个答案更多的是讨论OP提出的方法是否合适 。)
你真的不需要任何特别的东西。 createFileFromLongRunningComputation
方法不需要任何特殊的东西,只要确保你await
其中的一些异步方法,并且ConfigureAwait(false)
应该避免死锁,假设你没有做任何不寻常的事情(可能只是文件I / O,给定方法名称)。
警告 :
这是有风险的。 如果任务花费太长时间完成,ASP.net很可能会在这种情况下从你身下拉出来。
正如其中一位评论者指出的那样,有更好的方法可以实现这一目标。 其中之一是HostingEnvironment.QueueBackgroundWorkItem
(仅在.NET 4.5.2及更高版本中可用)。
如果长时间运行的计算需要相当长的时间才能完成,那么你可能最好完全不让它远离ASP.net。 在这种情况下,更好的方法是使用某种消息队列,以及在IIS / ASP.net之外处理这些消息的服务。