27

以下は、私が問題を抱えているコードの簡略版です。これをコンソールアプリケーションで実行すると、期待どおりに機能します。すべてのクエリは並行して実行され、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;
        }
    }           
}
4

1 に答える 1

52

Task.WaitAllを使用する必要はありませんawait Task.WhenAll

ASP.NETには、実際の同期コンテキストがあります。これは、すべてのawait呼び出しの後に、継続を実行するためにそのコンテキストにマーシャリングされることを意味します(これらの継続を効果的にシリアル化します)。コンソールアプリには同期コンテキストがないため、すべての継続はスレッドプールに送信されるだけです。リクエストのコンテキストで使用Task.WaitAllすることにより、リクエストをブロックします。これにより、他のすべてのタスクからの継続を処理するためにリクエストが使用されるのを防ぎます。

また、ASPアプリでのasync / awaitの主な利点の1つは、リクエストの処理に使用しているスレッドプールスレッドをブロックしないことです。あなたが使用する場合、Task.WaitAllあなたはその目的を打ち負かしています。

この変更を行うことの副作用は、ブロッキング操作から待機操作に移行することにより、例外が異なる方法で伝播されることです。スローするのではなくAggregateException、根本的な例外の1つをスローします。

于 2012-10-19T19:57:32.557 に答える