2013-07-11 159 views
4

我有一組Task(很多很多,大約400個):等待大量的任務

IEnumerable<Task> tasks = ... 

我想在同一時間運行它們,然後等待他們中的每一個。我使用這段代碼運行的任務:

Task.Run(async() => { ... }); 

每個任務的運行異步方法本身,這就是爲什麼我需要在拉姆達的async關鍵字。在這些嵌套的任務中,出現了衆所周知的HTTP請求和接收到的HTTP響應。

我嘗試了兩種不同的方式來等待所有的任務來完成:

await Task.WhenAll(tasks); 

foreach (var task in tasks) 
{ 
    await task; 
} 

其中,先驗的,看起來完全一樣給我(當然他們不看起來不然,我不會在這裏張貼第一位...)。

第一種方式使任務運行速度更快,但在輸出窗口中有大量的A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll和其他類似信息。而且,有些任務仍在WaitingForActivation狀態之後致電await Task.WhenAll()

第二種方法是慢,它看起來像的任務不同時運行(我收到由一個HTTP響應中的一個,而等待任務的第一種方式讓他們來幾乎都在同一時間)。另外,當我使用foreach循環等待每個任務並且在循環後沒有任何任務具有WaitingForActivation狀態時,我在輸出窗口中完全看不到first chance exception

我知道等待一組任務的「最佳」方式是使用WhenAll()(至少爲了便於閱讀),但爲什麼這兩個方法的行爲不同?我怎樣才能克服這個問題?理想情況下,我希望任務能夠快速運行,並確保一切都結束(我有一個trycatchfinally塊在lambda中處理服務器錯誤,我沒有忘記finally中的if(httpClient != null) httpClient.Dispose(),然後任何人都會問......)。

歡迎任何提示!

編輯

好吧,我試過另外一件事。我說:

.ContinueWith(x => System.Diagnostics.Debug.WriteLine("#### ENDED = " + index))); 

每個任務,index作爲Task的數量。 當使用foreach循環,我得到:

#### ENDED = 0 
#### ENDED = 1 
#### ENDED = 2 
#### ENDED = 3 
#### ENDED = 4 
... 

當使用WhenAll(),我得到:

#### ENDED = 1 
#### ENDED = 3 
#### ENDED = 0 
#### ENDED = 4 
#### ENDED = 8 
... 

因此,使用foreach循環使我的所有任務同步運行...這也許可以解釋爲什麼我在輸出窗口中沒有得到任何First Chance Exception,因爲系統根本沒有被算法強調。

EDIT2:

示例代碼:http://pastebin.com/5bMWicD4

它使用可以在這裏找到一個公共服務:http://timezonedb.com/

+0

這並沒有使他們同步運行(他們已經運行),但你得到的結果,同時,並在訂貨您創建它們 –

+0

在你'await'一個'Task'應該不會影響如何是的方式該任務已執行。這真的是你的代碼,而不是像'foreach(var job in jobs){等待Task.Run(...); ''?你可以發佈一個簡短但完整的示例代碼來證明這一點嗎? – svick

+0

@svick我在我的文章中添加了一個示例代碼:-) – Max

回答

5

的兩次嘗試是完全不同的。

第一次嘗試等待所有任務完成並在之後繼續。它只會在所有任務完成後纔會拋出。 結果的順序不確定,取決於哪個任務先完成

第二個等待每個任務一個接一個,按順序將它們放在任務數組中,這當然不是你想要的,而是比較慢。即使一個任務失敗,它也會中止等待併發生異常。其他任務的結果將會丟失。

這不像是同步運行任務,因爲某些任務會比其他任務更早完成,但是您仍然必須一次檢查所有任務。

這裏您應該注意,Task.WhenAll不會自行阻止。它返回一個任務,當所有其他任務完成時結束。通過致電await Task.WhenAll您等待該任務完成。您可以檢查該任務的狀態,以查看是否有一個或多個子任務失敗或被取消,或通過調用ContinueWith來處理結果。

你也可以撥打Task.WaitAll而不是await Task.WhenAll阻塞,直到所有任務完成,或其中至少一個取消或中止。這有點類似於你的第二次嘗試,儘管它仍然避免了一個接一個地等待所有任務。

事實上,你有很多例外與你等待的方式無關。一次可以對同一個域(即地址)做多少個HTTP連接是有限制的,可能會有超時錯誤(通常是由連接限制引起)或其他網絡相關問題。

您收到的例外情況受到您是否撥打await Task.WhenAllTask.WaitAll的影響。 This post explains the issue,但簡而言之,Task.WaitAll將收集所有異常並拋出AggregateException,而await Task.WhenAll將只返回其中的一個。

順便說一下,您收到的SocketException消息是什麼?

+0

謝謝你的回答。如果我理解正確,只有在任務明確等待時才執行'ContinueWith()'中的lambda,但是當任務自行結束時才執行lambda?關於'第一次機會Exception',我沒有得到任何消息,即使我在周圍所有的'等待WhenAll()'用''try'塊catch'打電話。有沒有辦法知道幕後發生了什麼? – Max

+0

「其他任務的結果將會丟失。」即使使用Task.WhenAll(),它們也會「丟失」。如果任何'任務'故障,則返回的'任務'也出現故障。但是你可以隨時訪問原始的「任務」。 – svick

+0

「結果的順序是不確定的,取決於哪個任務先完成。」文檔中另有說明:[「返回任務的'Result'將被設置爲一個數組,其中包含所有提供的任務的結果* *與提供的順序相同**。「](http://msdn.microsoft.com/zh-cn/library/hh194874.aspx) – svick

4

您的代碼的行爲與await無關。它是由迭代Task的集合的方式引起的。大多數LINQ方法是懶惰的,這意味着它們只有在迭代它們時纔會執行它們的代碼。

因此,該代碼開頭的每個Task後,才上一個完成:

foreach (var task in tasks) 
{ 
    await task; 
} 

但是這個代碼啓動所有這些一次:

foreach (var task in tasks.ToList()) 
{ 
    await task; 
} 

而且,由於Task.WhenAll()做的ToList()相當於在內部,您將獲得與上面第二個片段相同的行爲。

+0

謝謝你的回答。還有一些我不明白的地方,我使用了'Task.Run',它不會自動運行'Task'嗎?我認爲創建'Task'並在稍後啓動它的唯一方法是使用構造函數'new Task()'本身。 – Max

+1

@RedPolygon是的,但是該代碼位於不立即執行的lambda中,只在迭代結果序列時執行。 – svick