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

What's the correct way to run multiple parallel tasks in an asp.net process?

我想我不明白什么。我原以为 Task.Yield() 强制为任务启动一个新的 thread/context 但在 re-reading this answer 上似乎它只是强制该方法是异步的。它仍将处于相同的上下文中。

在 asp.net 进程中并行创建和 运行 多个任务而不导致死锁的正确方法是什么?

换句话说,假设我有以下方法:

async Task createFileFromLongRunningComputation(int input) { 
    //many levels of async code
}

并且当某个POST路由被命中时,我想同时启动上述方法3次,return立即,但是当所有三个完成了。

我想我需要在我的行动中加入这样的东西

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

是正确的。这个答案更多的是讨论OP提出的方法是否是一个好方法。)

您真的不需要任何特别的东西。 createFileFromLongRunningComputation 方法不需要任何特殊的东西,只要确保你在其中 awaiting 一些异步方法并且 ConfigureAwait(false) 应该 避免死锁,假设你没有做任何不寻常的事情(可能只是文件 I/O,给定方法名称)。

警告:

这是有风险的。 ASP.net 如果任务需要很长时间才能完成,那么在这种情况下,ASP.net 很可能会把地毯从你身下拉出来。

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

如果长时间的 运行 计算需要很长时间才能完成,您最好将其完全排除在 ASP.net 之外。在那种情况下,更好的方法是使用某种消息队列,以及在 IIS/ASP.net.

之外处理这些消息的服务

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

ASP.Net 中后台处理的最佳解决方案(您的 AppDomain 可以 recycled/shut 与您的所有任务一起自动关闭)在 Scott Hanselman and Stephen Cleary 的博客中(例如 HangFire)

但是,您可以 结合使用 Task.YieldConfigureAwait(false) 来达到同样的目的。

Task.Yield 所做的只是 return 一个等待程序,它确保方法的其余部分不会同步进行(通过 IsCompleted return falseOnCompleted 立即执行 Action 参数)。 ConfigureAwait(false) 忽略 SynchronizationContext,因此强制方法的其余部分在 ThreadPool 线程上执行。

如果你同时使用两者,你​​可以确保 async 方法 return 立即成为一个任务,它将在 ThreadPool 线程(如 Task.Run)上执行:

async Task CreateFileFromLongRunningComputation(int input)
{
    await Task.Yield().ConfigureAwait(false);
    // executed on a ThreadPool thread
}

编辑: George Mauer 指出,由于 Task.Yield returns YieldAwaitable 你不能使用 ConfigureAwait(false) 这是 Task class 上的方法。

您可以通过使用 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 问题的回答。