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

sec*_*ean 26 c# asp.net task-parallel-library async-await

下面是我遇到问题的代码的简化版本.当我在控制台应用程序中运行它时,它按预期工作.所有查询都是并行运行的,并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<Foo> GetFooAsync(int value)
{
    Foo foo = null;

    Func<Foo, Task> 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<SqlCommand, Task> executeCommand, int value)
{
    using (SqlConnection connection = new SqlConnection(...))
    {
        connection.Open();

        using (SqlCommand command = connection.CreateCommand())
        {
            // Set up query...

            await executeCommand(command);

            // Log results...

            return;
        }
    }           
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*rvy 51

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

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

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

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

  • 一个重要的警告是await Task.WhenAll不会抛出AggregateException; 只会传播其中一个内部异常.如果要检查整个AggregateException,则需要从task.WhenAll存储对返回任务的引用,并显式检查其Exception属性. (4认同)