Asynchronous Programming - 第十四章 :async lambda
新的一年,動力滿格,誓言要變成更好的自己,我們開始報名課程、立下目標、說要健身、要學語言、要創業,那一刻就像丟出一個 Task 嚷嚷著我會做到的承諾。但接下來,沒有積極安排「等待與驗收」,例如固定回顧、截止日、交付物、找人督促,生活的 ForEach 將忙碌的人生繼續往下跑,我們會很快進入下一堆事情。最後你回頭看 results 可能是空的、有的只有一半(做到一半被別的事用掉了)、有時看起來做了很多,但沒有一個真正可用的成果,問題不在是否努力,是因為沒設計「誰來等你完成」!!!

1 | urls.ForEach(async url => |
實際上這段 code 有兩個問題

Fire and Forget:ForEach本身不會等待內部的async動作完成,等於是「丟出去就不管了」
async 「先把控制權還出去,等結果準備好再回來」,所以拿到的是「承諾會有結果的 Task」,而不是結果本身
- async Lambda 被建立,這個 Lambda 的型別是:Func<string, Task>,它「不是馬上做完事情」,而是「回傳一個 Task」
- 執行到 await client.GetAsync(url),HTTP Request 發出去,還沒拿到回應時,這個方法「暫停」,執行緒被釋放回 Thread Pool(重點)
- HTTP 回來後,繼續往下跑,讀取 response content,組成字串整個 Lambda 結束
- Task 標記為 Completed「未來的結果」才正式完成,但 ForEach 跑完,整個方法會直接往下執行,所以可能會看到results 有時是空的、有時只有一半、有時還沒加完就被用掉了
👉 問題不在 async 本身,而在「誰有在等這個 Task」



Thread Safety:results 是共用的字典,在多個非同步任務同時Add()時,可能會出現執行緒安全(Thread Safety)問題

1 | foreach (var url in urls) |
這樣的寫法雖然「安全又可預期」,但每個請求都要等上一個完成後才能繼續下一個,是「排隊」的概念

1 | var tasks = urls.Select(async url => { |
這段才是「真正的非同步」,他透過 Select() 建立一組非同步任務(每個 URL 對應一個 Task),並且使用 Task.WhenAll() 等待所有任務完成,回傳的結果可以整齊地整理成字典(Dictionary)

1 | urls.Select(async url => { ... }); |
因為 LINQ 的 Select() 是 延遲執行(Deferred Execution) 的,它只是一個「規劃」,直到你真的去「取用結果」時(例如 .ToList()、.Count()、await Task.WhenAll()),它才會開始跑

1 | async Task Main() |


All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.








