在asp.net进程中运行多个并行任务的正确方法是什么?

时间:2015-01-16 15:49:22

标签: c# asp.net multithreading task-parallel-library async-await

我想我不理解某事。我原以为Task.Yield()强制为一个任务启动一个新的线程/上下文,但在re-reading this answer上它似乎只是强制该方法是异步的。它仍将处于相同的背景下。

在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是正确的,但显然不是。

2 个答案:

答案 0 :(得分:6)

正确将并发工作卸载到不同线程的方法是使用Task.Run作为rossipedia建议。

ASP.Net 中的后台处理的最佳解决方案(AppDomain可以与所有任务一起自动回收/关闭)Scott Hanselman和{ {3}}的博客(例如HangFire)

但是,您可以Task.Yield一起使用ConfigureAwait(false)来实现相同目标。

所有Task.Yield会返回一个等待者,确保该方法的其余部分不会同步进行(让IsCompleted返回falseOnCompleted执行立即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,您无法使用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)(); } } } 相同的内容:

{{1}}

这不是推荐,它是对Task.Yield问题的回答。

答案 1 :(得分:4)

l3arnon's answer是正确的。这个答案更多的是讨论OP提出的方法是否合适。)

你真的不需要任何特别的东西。 createFileFromLongRunningComputation方法不需要任何特殊内容,只需确保await中有一些异步方法,ConfigureAwait(false) 应该避免死锁,假设你没有做任何与众不同的事情(可能只是文件I / O,给定方法名称)。

<强>买者

这很危险。如果任务需要很长时间才能完成,ASP.net很可能会在这种情况下从你身下拉出来。

正如其中一位评论者指出的那样,有更好的方法可以实现这一目标。其中一个是HostingEnvironment.QueueBackgroundWorkItem(仅在.NET 4.5.2及更高版本中可用)。

如果长时间运行的计算需要很长时间才能完成,那么最好还是完全不要让它远离ASP.net。在这种情况下,更好的方法是使用某种消息队列,以及在IIS / ASP.net之外处理这些消息的服务。