2012-10-19 34 views
23

下面是我遇到的代碼的簡化版本。當我在控制檯應用程序中運行它時,它按預期工作。所有查詢均並行運行,並在全部完成時返回Task.WaitAll()Task.WaitAll掛在ASP.NET中的多個等待任務中

但是,當此代碼在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; 
     } 
    }   
} 

回答

46

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

在ASP.NET中,您有一個實際的同步上下文。這意味着畢竟await調用您將被編組回到該上下文執行延續(有效地序列化這些延續)。在控制檯應用程序中沒有同步上下文,因此所有延續都只發送到線程池。通過在請求的上下文中使用Task.WaitAll,您將阻止它,從而阻止它被用於處理來自所有其他任務的延續。

另請注意,ASP應用程序中async/await的主要優點之一是而不是會阻止您用於處理請求的線程池線程。如果你使用Task.WaitAll,你就是爲了達到這個目的。

使這種變化的一個副作用是,通過從阻塞操作移動到AWAIT操作異常將被不同地傳播。它不會拋出AggregateException它會拋出一個潛在的例外。

+1

+1。雖然我會使用術語「請求上下文」而不是「主線程」。 –

+0

@StephenCleary是的,這是有爭議的。我將主線程放在引號中,因爲它不是整個應用程序的主線程,但它可以被認爲是「該請求的主線程」。在我腦海中,這是思考問題的有用方式。 – Servy

+4

一個重要的警告是await Task.WhenAll不會拋出AggregateException;只有一個內部異常會被傳播。如果要檢查整個AggregateException,則需要存儲對task.WhenAll中返回的任務的引用,並明確檢查其Exception屬性。 –