在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.YieldConfigureAwait(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之外处理这些消息的服务。