2012-10-19 18 views
23

以下は、問題を抱えているコードの簡略化されたバージョンです。これをコンソールアプリケーションで実行すると、期待通りに機能します。すべてのクエリは並行して実行され、すべてが完了するとTask.WaitAll()が返されます。Task.WaitAllは、ASP.NETで待たされている複数のタスクにぶら下がっています。

ただし、このコードをWebアプリケーションで実行すると、リクエストがハングするだけです。デバッガを接続してすべてを中断すると、実行がTask.WaitAll()で待機していることが示されます。最初のタスクは完了しましたが、他のタスクは完了しません。

私はASP.NETで動作しているときにハングする理由を理解できませんが、コンソールアプリケーションで正常に動作します。あなたがawait Task.WhenAllを使用する必要が

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; 
     } 
    }   
} 

答えて

46

よりもむしろTask.WaitAll

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

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

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

+1

+1。私は "メインスレッド"ではなく "要求コンテキスト"という言葉を使用します。 –

+0

@StephenClearyうん、議論の余地があった。メインスレッドはアプリケーション全体のメインスレッドではないので、引用符で囲みますが、「その要求のメインスレッド」と考えることができます。それは私の心の中で問題を考えるのに役立つ方法です。 – Servy

+4

重要な注意点の1つは、Task.WhenAllがAggregateExceptionをスローしないことを待つことです。内側の例外の1つだけが伝搬されます。 AggregateException全体を調べる場合は、task.WhenAllから返されたタスクへの参照を格納し、Exceptionプロパティを明示的に調べる必要があります。 –

関連する問題