Asynchronous Programming - 第十二章:章魚的八爪例外:多執行緒的捕獲術
在深海裡,章魚的八隻腕足總是同時探向不同的方向,纏繞著礁岩、觸碰著漩渦,也悄悄在黑暗中編織出一張未被看見的網。我們的非同步程式,就像這隻章魚,當你派出無數任務游向未知,若沒有好好等待(await),有些錯誤就會像溜走的墨汁般散開,再也無法追溯。
若選擇用同步阻塞強行捕捉,例外也會被包裹成難解的聚合結,像是八爪同時纏住獵物卻黏成一團。學會如何等待,如何攤開多執行緒裡潛藏的觸手,才是馴服這隻章魚、看清每一道潛伏例外的唯一方法。
🐙
🎵 實驗一:沒 await,什麼都接不到
1 | public static async Task ExceptionResultTest() |
執行後什麼都沒有。錯誤直接遺失,因為 try…catch 並沒有真正等待這個 Task 的完成。async 方法呼叫後立即傳回 Task。try 區塊在呼叫後就結束了,例外發生時已離開 try 區塊。
🎵 實驗二:用 .Result 或 .Wait() 阻塞取得結果
1 | public static async Task<string> ExceptionResultTest() |
成功抓到錯誤。但訊息是 AggregateException: One or more errors occurred。需要看 InnerException 才知道真正錯誤。.Result 與 .Wait() 是同步阻塞。若 Task 出現例外,CLR 會將例外包成 AggregateException。這是 .NET 的多任務例外聚合機制。
🎵 實驗三:使用 await 等待結果
1 | public static async Task<string> ExceptionResultTest() |
直接捕捉到 NotImplementedException。訊息清楚明確,堆疊追蹤與除錯資訊完整。await 會將 Task 中發生的例外重新拋出(unwrap)。因此例外型態不被包成 AggregateException,除錯體驗佳。
🎵 實驗四:Task 不回傳值也一樣
1 | public static async Task ExceptionResultTest() |
🎵 多個 Task 的例外處理
如果一次執行多個 Task,用 .WaitAll() 或 .Wait(),所有例外都會被聚合:
1 |
|
若用 await Task.WhenAll(),則第一個出現的例外就會被拋出,且同樣是原型別,不是 AggregateException。
✅ 最佳實踐
非同步永遠要 await, 除非有特別需求(如 Fire-and-Forget),否則不要只是丟出 Task。
同時多任務,盡量用 await Task.WhenAll, 不要用 .WaitAll(),除非需要同步阻塞。
理解聚合例外的機制, .Wait() 與 .Result 的 AggregateException 並非 bug,而是多執行緒任務例外安全聚合機制。
在 Library 或 API 層面提供非同步介面, 讓呼叫端決定是否要 await,保留彈性。
🐙 結語
章魚從不害怕放開八爪去探索,它知道每一條觸手都可能捕捉到不同的訊息,也可能同時失落在暗潮裡。
非同步程式也是如此,Task 被派遣到多執行緒的深海中,若沒有耐心等待(await),有些錯誤終將像失去的墨汁,永遠融進黑暗裡。
當你學會在適當的時機攤開每一隻觸手、用 await 解開包裹的聚合結,就能在錯誤發生時看見最真實的輪廓,讓除錯不再是混沌的漩渦,而是一場與章魚共舞的優雅對話。
願你的程式,也能像這隻八爪章魚一樣,靈活捕捉每個潛伏的風暴,帶著錯誤浮出水面,然後再次優雅地潛回深海,繼續未完的探索。