只有累積,沒有奇蹟

2024年1月15日 星期一

[NET] 再探 Task.WaitAll 與 Task.WhenAll 差異

前言
之前自己對於 Task.WaitAll 與 WhenAll 有些一知半解的地方,因此進行研究後並撰寫了 [.NET] Task 等待多個任務 - Task.WaitAll 與 Task.WhenAll 文章,最近在與公司同事 Code Review 再度討論起兩者的使用方式,主管也不吝的與團隊成員分享對於 WaitAll 與 WhenAll 主要的看法與使用上的差異,這篇筆記簡單記錄當時討論的內容與結論,內容若有問題歡迎提出來一起討論。

差異性
首先,在之前文章[.NET] Task 等待多個任務 - Task.WaitAll 與 Task.WhenAll 中思考的出發點是以效率出發,而建議使用 Task.WhenAll 取代 Task.WaitAll 方法,但可能在使用上還是有其他需要考慮的部分,其實效率並不是兩個方法的主要差異,差異性有以下兩點

例外處理 Exception
  1. Task.WaitAll() 處理所有返回 Task Exception
  2. Task.WhenAll() 只能處理第一個返回 Task Exception
我們接下來可以透過簡單的 Code 來說明例外處理的部分,下列代碼定義了兩個 Function 分別是 throw IndexOutOfRangeException 與 NullReferenceException,在 main 的方法使用 WaitAll 並使用 try catch 將觀察例外處理 exception 部分為何
  1. class Program
  2. {
  3. static async Task Main(string[] args)
  4. {
  5. try
  6. {
  7. Task.WaitAll(ThrowIndexOfRangeException(), ThrowNullReferenceException());
  8. }
  9. catch (Exception ex)
  10. {
  11. Console.WriteLine(ex);
  12. }
  13. }
  14. static private Task ThrowIndexOfRangeException()
  15. {
  16. return Task.Run(() => { throw new IndexOutOfRangeException();});
  17. }
  18.  
  19. static private Task ThrowNullReferenceException()
  20. {
  21. return Task.Run(() => { throw new NullReferenceException(); });
  22. }
  23. }
開啟 Rider在 catch 下中斷點,可以從 Console 可以看到有抓到兩個 exception,拋出的例外 excption 都有抓到看起來合情合理
接著將 WhenAll 來替換定原有的 WaitAll 方法,再重新跑一次觀察例外處理 exception 是否相同
  1. static async Task Main(string[] args)
  2. {
  3. var tasks = new [] { ThrowIndexOfRangeException(), ThrowNullReferenceException() };
  4. try
  5. {
  6. await Task.WhenAll(tasks);
  7. }
  8. catch (Exception ex)
  9. {
  10. Console.WriteLine(ex);
  11. }
  12. }
執行結果如下
可以發現 exception 僅僅抓到第一個拋出的例外也就是 indexOutOfRangeException,並不會抓到拋出來的第二個例外,也就是說 catch 抓到的 exception 僅僅是第一個 exception 其餘的都被河蟹掉了(希望的可能是 catch 到所有的例外),有開發者在微軟的 Github 反映 Task.WhenAll - Inner exceptions are lost #31494 此問題,內部有針對此問題做深入討論,原先設計方向是使用 WhenAll 時會發生例外時會將錯誤包在 AggregateException 中,後來因為下列原因修改這設計(怕誤解意思直接使用原文)
  1. a) the vast majority of such cases had fairly homogenous exceptions, such that propagating all in an aggregate wasn't that important
  2. b) propagating the aggregate then broke expectations around catches for the specific exception types, and
  3. c) for cases where someone did want the aggregate, they could do so explicitly with the two lines like I wrote.
了解設計初衷之後,如果還是希望在 WhenAll 取得所有的例外可以參考討論串中 noseratio 的回答撰寫 Task 擴充方法蒐集 exception ,代碼如下
  1. public static Task WithAggregatedExceptions(this Task @this)
  2. {
  3. return @this.ContinueWith(ante =>
  4. ante.IsFaulted &&
  5. (ante.Exception.InnerExceptions.Count > 1 ||
  6. ante.Exception.InnerException is AggregateException) ?
  7. Task.FromException(ante.Exception.Flatten()) :
  8. ante,
  9. TaskContinuationOptions.ExecuteSynchronously).Unwrap();
  10. }
加入擴充方法後,下一步就是將既有的 WhenAll 代碼加上 WithAggregatedExceptions 擴充方法
  1. static async Task Main(string[] args)
  2. {
  3. var tasks = new [] { ThrowIndexOfRangeException(), ThrowNullReferenceException() };
  4. try
  5. {
  6. await Task.WhenAll(tasks).WithAggregatedExceptions();
  7. }
  8. catch (Exception ex)
  9. {
  10. Console.WriteLine(ex);
  11. }
  12. }
再重新看一下 Catch 到的錯誤種類,即可發現被河蟹掉的都找的到了 (大師兄回來了
主線程阻塞
  1. 是否會造成主線程雍塞,影響用戶使用
另一個要關注的點是使用後是否會造成阻塞的狀況,由於主線程雍塞的議題已經不是一兩天的事,這裡就簡單整理大神所提到的重點
ASP.NET async 基本心法
閱讀筆記 - 使用 .NET Async/Await 的常見錯誤
.NET 程式鎖死與 SynchronizationContext
或是參考由強者前同事所撰寫的電子書 : .NET 本事-非同步程式設計 :)


感想
魔鬼藏在細節裡。以上就針對 Task.WaitAll 與 Task.WhenAll 做更進一步的說明,以及在討論時所提到的兩個主要的差異內容,這些細節如果一沒有注意到勢必會造成很大的影響,在開發使用也請多加留意或是查相關資料。

參考
C# Thread: What is the difference between Task.WaitAll & Task.WhenAll
Why doesn't await on Task.WhenAll throw an AggregateException?
Task.WhenAll Method
Task.WhenAll - Inner exceptions are lost




Related Posts:

  • [.NET] 如何取得 Enum 的 Description 描述字串前言  列舉類型 Enum 在 C# 很常用的一種類型,所允許的型別必須是byte、sbyte、short、ushort、int、uint、long、ulong,在使用上沒特別指定的話基本類型是 int,對我自己來說在程式中使用 Enum 而不用 int 的好處是 Code 閱讀上比較清晰,舉例來說在閱讀代碼時第一段代碼使用 Enum 更容易讓人好懂些 if (code == ResponseCode.OK) //… Read More
  • [AWS] 使用 AWS SDK上傳檔案到 AWS S3前言 最近在與第三方做串接時,有個需求是要將圖片存起來放在 Application Server 上,與同事討論建議將圖片放在AWS S3 上,多年前有微軟剛推出 Azure 時有接觸過一些,但這幾年隨著年紀增長早已忘光光,這篇簡單紀錄 C# 透過 AWS SDK 將圖片上傳到 AWS S3 上的步驟與要注意的細節 AWS S3  Amazon Simple Storage Service 功能簡稱 S3,是Amazon的物件儲存服務,… Read More
  • [.NET] ASP.NET Application 概述 ASP.NET 處理請求的兩個步驟 當用戶發送一個請求到IIS(這裡指IIS 5.0 & IIS 6.0),ASP.NET 處理請求的步驟有兩種 1. 建立一個可以處理用戶端傳來請求Request的環境(ASP.NET Environment),包括建立application object(應用程式)、Ruquest、Response、Context Object等物件來處理此Ruquest請求。 2. 環境被建立後,應用程式會透過m… Read More
  • [.NET] ASP.NET 狀態管理(State Management):Session Session 狀態支援數種不同的資料儲存選項,簡單描述 Session 可用的狀態模式 InProc:存在Web伺服器的記憶體中;ASP.NET中預設的Session狀態設定,是最常用也是最方便的 Session 狀態模式,缺點是如果重新啟動伺服器,所有的Session資料將會遺失。 StateServer:儲存在 ASP.NET 狀態服務的處理序中;可以確保 Session 在 Web 應用程式重新啟動時保留下來,並且讓 Web 伺服陣… Read More
  • [C#] Anonymous Type 匿名型別說明 Anonymous Type 是甚麼? 匿名型別是C# 3.0開始有的特性,是一種暫存型的型別,不需要建立額外的類別來存放資料 根據MSDN對於 匿名型別 的說明如下 根據MSDN的說明,整理一下重點及特性 1. 透過 new 建立實體  // Anonymous Typevar employee = new { Id = 1, Name = "Marcus", Age = 22 } … Read More

0 意見:

張貼留言

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

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com