[C#] .Result、async void、迴圈 await 的真實後果

2025-07-28

在 C# 裡,async/await 大概是大家寫最多,但理解最少的語法。

很多人使用 async,只是因為 "不加就跳警告,所以我加" , 至少我有時候是因為這樣

尤其是在試跑 open soruce 的專案


如果你也是這樣,那可能要稍微停一下,async/await 不是什麼魔法關鍵字

用錯不但不會變快,還可能讓效能更差、ThreadPool 卡死,甚至造成 deadlock

接下來我把 async/await 拆開來講

講底層運作、真實陷阱、什麼時候該用、什麼時候不要用

1. 非同步到底是什麼?一句話講完 : 非同步不是讓程式"跑得更快",非同步是讓你"不要塞住"

當你在做 I/O(API call、畫面 render)時,執行緒完全沒事做。

讓它卡住等三秒是浪費,所以 async 的設計就是

I/O 還沒回來 → 請你先去忙別的

I/O 回來 → 回來把剩下的行程跑完

完全不是多執行緒,而是"把CPU借出去"

async/await 底層其實超複雜,所以如果你專案要全面改寫你只會想說句 WTH 

編譯器看到 await 時做的事,比你想像中多:

-先生成一個 state machine

-把方法拆成好幾段

-遇到還沒完成的 Task,執行緒先被釋放

-Task 完成後,用 callback 接續跑下一段

最後你只看到:

await DoSomethingAsync();</p>


2. 常見錯誤: .Result / .Wait() 導致 deadlock

var data = GetDataAsync().Result; //我個人很常用&#65292;不過都是用在寫程式範例上面

或是

GetDataAsync().Wait();

就在製造 deadlock 的可能性。

因為這兩個 API 會把執行緒整個堵住,而 async 方法的 continuation 剛好也想回到這條執行緒上執行。
結果兩邊互等

正確寫法

await GetDataAsync();


3.少用或不要 async void ,寫成 async void 的方法,基本上你就失去控制了

更改寫法

async Task DoSomethingAsync();


4.  大量 CPU 工作使用 async ,因為有時候這需要實測判斷,如果發現他是大量 CPU 運作

你加上了有時候不會比較好,建議使用 Parallel.For、SIMD 等平行化方式處理

public async Task<int> Calc() { return HeavyWork(); }

正確做法

public Task<int> Calc() { return Task.Run(() => HeavyWork()); }


5. 在迴圈裡連續 await

這是 async 最常寫出的反效果程式片段

foreach (var url in urls) { var data = await client.GetAsync(url); }

這邊問題會導致互卡

較正確做法

var tasks = urls.Select(url => client.GetAsync(url)); var results = await Task.WhenAll(tasks);


做個結論 - 

如果你先弄懂 async 的核心概念(重點是不阻塞),也知道它背後其實是靠 state machine 在切換流程

再避開幾個常見雷(例如不要用 .Result、不要寫 async void、也不要在迴圈裡每次都 await)

那 async/await 真的能讓你的程式跑得更順、更有效率,也能處理更多 I/O 工作,維護起來也不會那麼痛苦

但如果你只是為了把警告消掉才加 async,那最後通常只會越寫越亂。要把 async 用好,還是得先理解它的運作方式




當麻許的碎念筆記 2014 | Donma Hsu Design.