只有累積,沒有奇蹟

2024年1月8日 星期一

[NET] Task 等待多個任務 - Task.WaitAll 與 Task.WhenAll

前言 
在開發偶爾會遇到需要起多個 Task ,接著等待這些 Task 都完成在去做後續邏輯處理,.NET 中提供 Task.WaitAll 與 Task.WhenAll 靜態方法來知道所有任務是否執行完成,過去自己對於兩者的差異性不太明白,因此這篇文章整理自己對於兩者的相關資訊與用法,希望有不清楚或是自己研究錯誤的地方歡迎提出討論

探索問題
Task.WaitAll
在以下的 Sample Code 中使用 Task.Run 建立三個 Task 分別 sleep 1、2、3 秒鐘,接著使用 Task.WaitAll 方法來知道三者是否已執行完成 
  1. static void Main(string[] args)
  2. {
  3. Task Task1 = Task.Run(() => Thread.Sleep(1000));
  4. Task Task2 = Task.Run(() => Thread.Sleep(2000));
  5. Task Task3 = Task.Run(() => Thread.Sleep(3000));
  6. Task.WaitAll(Task1, Task2, Task3);
  7. // todo something..
  8. }
在 Task.WaitAll 有提供另一組 API ,可以限定想要等待的時間秒數才不用一直無止境等待下去,這裡在原本的 sample code 再加上等待時間 2.5 秒及透過 Task.IsCompleted 顯示各自是否已完成
  1. static void Main(string[] args)
  2. {
  3. Task Task1 = Task.Run(() => Thread.Sleep(1000));
  4. Task Task2 = Task.Run(() => Thread.Sleep(2000));
  5. Task Task3 = Task.Run(() => Thread.Sleep(3000));
  6.  
  7. Task.WaitAll(new Task[] {Task1, Task2, Task3 }, 2500);
  8.  
  9. Console.WriteLine("Task1.IsCompleted:{0}", Task1.IsCompleted);
  10. Console.WriteLine("Task2.IsCompleted:{0}", Task2.IsCompleted);
  11. Console.WriteLine("Task3.IsCompleted:{0}", Task3.IsCompleted);
  12. }
  13.  
  14. // result
  15. // Task1.IsCompleted:True
  16. // Task2.IsCompleted:True
  17. // Task3.IsCompleted:False
  18.  
在非同步情境時使用 WaitAll 會阻礙執行緒或鎖定 ( blocks thread ),會造成在所有的工作結束之前,當前使用到的執行緒無法自由處理其他工作,在此篇 How and Where Concurrent Asynchronous I/O with ASP.NET Web API 文章有提到,如果某項任務無法正確執行最後引起 deadlocks 狀況發生,此時需要使用 ConfigureAwait 來避免執行緒 lock,更詳細的內容可以參考 Best Practices in Asynchronous Programming

Task.WhenAll
另一個等待所有任務完成的方法是 Task.WhenAll,使用的 Sample Code 中是相同於上述代碼,使用方式不難,在 MSDN Task.Whenall 方法簽章可以看到,使用 Task.WhenAll 方法時會回傳 Task,因此與剛剛差異的是其中等待完成任務方法使用 WhenAll 進行,在使用一個 taskWhenAll 變數用 wait 方法等待完成
  1. static void Main(string[] args)
  2. {
  3. Task Task1 = Task.Run(() => Thread.Sleep(1000));
  4. Task Task2 = Task.Run(() => Thread.Sleep(2000));
  5. Task Task3 = Task.Run(() => Thread.Sleep(3000));
  6. var taskWhenAll = Task.WhenAll(Task1, Task2, Task3);
  7. taskWhenAll.Wait();
  8. }
在執行到 Task.WhenAll 時候,會新增一個 Task 並等待該任務的結果 (自己理解上是有專門 Task 在 handle 後續處理),因此使用 WhenAll 不會造成執行緒阻礙的情況發生

Task.WaitAll v.s Task.WhenAll
上面分別介紹兩者的用法與說明,但光看文字與簡單代碼還不過癮,因此小弟參考網路上資料針對 WaitAll 與 WhenAll 執行時間做比較讓數據說話,sample Code 如下
  1. public class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. IEnumerable<Worker> workerObjects = new List<Worker>
  6. {
  7. new Worker {Id = 1, SleepTimeout = 1000},
  8. new Worker {Id = 2, SleepTimeout = 2000},
  9. new Worker {Id = 3, SleepTimeout = 3000},
  10. new Worker {Id = 4, SleepTimeout = 4000},
  11. new Worker {Id = 5, SleepTimeout = 5000},
  12. };
  13. // WaitAll
  14. TaskWaitAll(workerObjects);
  15. // WhenAll
  16. var task = TaskWhenAll(workerObjects);
  17. Console.ReadKey();
  18. }
  19. static void TaskWaitAll(IEnumerable<Worker> workers)
  20. {
  21. var startTime = DateTime.Now;
  22. Console.WriteLine("Starting : Task.WaitAll...");
  23. Task.WaitAll(workers.Select(worker => worker.DoWork(startTime)).ToArray());
  24. var endTime = DateTime.Now;
  25. Console.WriteLine("Test finished after {0:F2} seconds.\n", (endTime - startTime).TotalSeconds);
  26. }
  27. static Task TaskWhenAll(IEnumerable<Worker> workers)
  28. {
  29. var startTime = DateTime.Now;
  30. Console.WriteLine("Starting test: Task.WhenAll...");
  31. var task = Task.WhenAll(workers.Select(worker => worker.DoWork(startTime)));
  32. task.Wait();
  33. var endTime = DateTime.Now;
  34. Console.WriteLine("Test finished after {0:F2} seconds.\n", (endTime - startTime).TotalSeconds);
  35. return task;
  36. }
  37. }
  38. public class Worker
  39. {
  40. public int Id;
  41. public int SleepTimeout;
  42. public async Task DoWork(DateTime testStart)
  43. {
  44. var workerStart = DateTime.Now;
  45. Console.WriteLine("Worker {0} started on thread {1}, beginning {2:F2} seconds after test start.", Id, Thread.CurrentThread.ManagedThreadId, (workerStart - testStart).TotalSeconds);
  46. await Task.Run(() => Thread.Sleep(SleepTimeout));
  47. var workerEnd = DateTime.Now;
  48. Console.WriteLine("Worker {0} stopped; the worker took {1:F2} seconds, and it finished {2:F2} seconds after the test start.", Id, (workerEnd - workerStart).TotalSeconds, (workerEnd - testStart).TotalSeconds);
  49. }
  50. }
簡單說明 sample code 內容
  • Worker 類別 : 提供 doWork async方法透過 task.run 執行 thread.sleep,可傳入要 sleep 時間與 id,執行前後分別記錄起始、結束時間 
  • main : 主要測試程式進入點,做三件事情
    • 初始化測試的 workerObjects,建立五筆 worker instance 分別測試 1~5 秒
    • 執行測試  TaskWaitAll、TaskWhenAll 方法 
  • TaskWaitAll 方法,裡面紀錄 waitAll 方法起始與結束的時間
  • TaskWhenAll 方法,裡面紀錄 whenAll 方法起始與結束的時間 
執行結果
從以下得知 Task.WaitAll 執行時間為 5.07 秒,Task.WhenAll 執行時間為 5.01 秒
執行多次時間比較都是 WhenAll 會優於 WaitAll,有興趣的客官可以自行下載試試

後記
為了避免執行緒阻塞的情形發生,使用上建議 Task.WhenAll 來取代 Task.WaitAll,從最後的簡單測試代碼執行時間比較來看也是 WhenAll 會優於 WaitAll,其中為了比較 Task.waitAll 與 Task.whenAll 差異性閱讀很多相關的文章與 blog,花了很多時間才產生這篇文章,希望可以透過以上的說明能幫助到有需要的網友(咦 誰是你網友)如果文章中有謬誤或不正確的部分,也請各位大大給予正確指教,最後推薦 MSDN 文章 : Best Practices in Asynchronous Programming 讀完對這方面會很有幫助,謝謝

參考
Async/Await - Best Practices in Asynchronous Programming
Using async/await for multiple tasks
How and Where Concurrent Asynchronous I/O with ASP.NET Web API
await, WhenAll, WaitAll, oh my!!

Related Posts:

  • [.NET] ASP.NET 狀態管理(State Management):ViewState Web應用程式是沒有狀態(Stateless)的,從前面介紹的文章(Application、Page Life-Cycle)可以了解到,每次用戶端發送請求(Request)到伺服器端時,都會建立Web網頁類別的新執行個體。由於每個請求都是新的個體,這也代表每次來回存取時,網頁及控制項的資訊將會遺失。例如,根據預設,當使用者在網頁textbox控制項輸入「Hello world」,按下按鈕送出後,這項資訊就會傳送到伺服器。但伺服器在回傳(Res… Read More
  • [C#] Named Arguments 具名引數前言  同事在開發專案時詢問某 method 有多個參數,有些參數是選擇輸入有預設值,因參數太多是否有方法可以指定參數名字 ? 否則一堆有設定預設值的在閱讀代碼會造成困擾,不小心也可能傳入錯誤變數,聽到當下建議抽成一個 Model 這樣更容易維護,但他表示由於引用的方法過多會擔心會牽一髮動全身,會有不可預期和毀滅性的災害發生,因此可以使用 具名引數 named arguments 來指定需要傳入的… Read More
  • [.NET] 字串加密 MD5、SHA1 常在開發中遇到需要將特定資料加密的動作,在儲存到資料庫中(比如說網站用戶的密碼加密後存到資料庫中,用戶在登入時,在把用戶輸入的密碼進行加密,再與資料庫密碼欄位比較是否一致)在.NET Framework中,可以透過 System.Security.Cryptography 命名空間來產生加密演算法的金鑰(註一),在用雜湊值(Hash Value)的加密方式達到目的,雜湊演算法將任意長度的二進位值對應到固定長度較小的二進位值,稱為雜湊值 (Ha… Read More
  • [.NET] QueryString 的那些事 概述 Querystring(查詢字串)是附加在網頁URL結尾的資訊。主要是在Url上傳遞資料,可能是一個搜尋字串、頁碼、某項特定的指標…或類似的東西 在網址結尾加上一個問號(?)開始,每一組參數都是用「&」區隔開來,是一種KEY / Value的組合。 舉例來說(參考上圖),在Yahoo首頁搜尋引擎輸入「Hello world」後,在按下搜尋按鈕,會導頁到查詢結果頁面,並將符合搜尋字串相關資料呈現出來。 運作原理是透過Querystrin… Read More
  • [.NET] Default 和 NativeImage 資料夾問題 收到同事反應公司 Server C:\ 硬碟空間嚴重不足,確認後發現 C:\errors 資料空間占了 25g 而且還以驚人的速度成長中,進到資料夾底下查看分 Default 與 NativeImage folder 存放各種應用程式 Log ,如下圖所示 其中竟然還有 w3wp.exe,到底這些是如何記錄的呢 ? 本篇文章簡單記錄解決問題的過程 解決方案 為了追根究柢,點開其中 w3wp.exe 資料… Read More

1 則留言:

  1. https://www.youtube.com/watch?v=dGyr6hiy-lE
    Task.WhenEach

    回覆刪除

Copyright © 2025 m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com