Task.WaitAll挂起了ASP.NET中的多个等待任务

下面是我遇到问题的代码的简化版本。 当我在控制台应用程序中运行它时,它按预期工作。 所有查询都是并行运行的, Task.WaitAll()在完成后返回。

但是,当此代码在Web应用程序中运行时,请求才会挂起。 当我附加一个调试器并中断所有时,它表明执行是在Task.WaitAll()上等待。 第一项任务已经完成,但其他任务都没有完成。

我无法弄清楚它在ASP.NET中运行时挂起的原因,但在控制台应用程序中运行良好。

 public Foo[] DoWork(int[] values) { int count = values.Length; Task[] tasks = new Task[count]; for (int i = 0; i < count; i++) { tasks[i] = GetFooAsync(values[i]); } try { Task.WaitAll(tasks); } catch (AggregateException) { // Handle exceptions } return ... } public async Task GetFooAsync(int value) { Foo foo = null; Func executeCommand = async (command) => { foo = new Foo(); using (SqlDataReader reader = await command.ExecuteReaderAsync()) { ReadFoo(reader, foo); } }; await QueryAsync(executeCommand, value); return foo; } public async Task QueryAsync(Func executeCommand, int value) { using (SqlConnection connection = new SqlConnection(...)) { connection.Open(); using (SqlCommand command = connection.CreateCommand()) { // Set up query... await executeCommand(command); // Log results... return; } } } 

而不是Task.WaitAll你需要使用await Task.WhenAll

在ASP.NET中,您有一个实际的同步上下文。 这意味着在所有await调用之后,您将被编组回到该上下文以执行延续(有效地序列化这些延续)。 在控制台应用程序中没有同步上下文,因此所有延续都只是发送到线程池。 通过在请求的上下文中使用Task.WaitAll ,您将阻止它,这阻止它被用于处理来自所有其他任务的延续。

另请注意,ASP应用程序中async / await的主要优点之一是阻止您用于处理请求的线程池线程。 如果你使用Task.WaitAll你就是在打败那个目的。

进行此更改的一个副作用是,通过从阻塞操作转移到等待操作,exception将以不同方式传播。 它不会抛出AggregateException ,而是抛出一个潜在的exception。