async/await 的真相:不是多執行緒
最大的誤解
❌ "async/await 會開新執行緒處理"
✅ "async/await 讓執行緒在等待時去做別的事"
比喻
你在餐廳點餐:
- 同步:你站在櫃檯等,餐好了才離開(執行緒卡住等 I/O)
- 非同步:你點完拿號碼牌回座位,叫號再去拿(執行緒被釋放,I/O 完成後回來)
// 同步:執行緒等 1 秒,這段時間什麼都不能做
var data = httpClient.GetStringAsync(url).Result; // ← 阻塞!
// 非同步:執行緒被釋放去處理其他 Request
var data = await httpClient.GetStringAsync(url); // ← 不阻塞
I/O-bound vs CPU-bound
| 類型 | 例子 | 該用什麼 |
|---|---|---|
| I/O-bound | 資料庫查詢、HTTP 請求、檔案讀寫 | await(釋放執行緒等 I/O) |
| CPU-bound | 影像處理、加密計算、大量迴圈 | Task.Run()(真的開新執行緒) |
// I/O-bound → await
var users = await _db.Users.ToListAsync(); // 執行緒被釋放等 DB 回應
// CPU-bound → Task.Run
var hash = await Task.Run(() => ComputeExpensiveHash(data)); // 丟到背景執行緒
為什麼 Web 需要 async?
同步的 Web Server(假設 100 個執行緒):
→ 100 個 Request 同時進來
→ 每個都在等 DB(500ms)
→ 第 101 個 Request:「沒有空的執行緒了!」→ 503 Service Unavailable
非同步的 Web Server:
→ 100 個 Request 同時進來
→ await DB 時,執行緒被釋放
→ 釋放的執行緒去處理第 101、102... 個 Request
→ DB 回應後,繼續處理原本的 Request
→ 同樣 100 個執行緒,能處理數千個並行 Request
常見陷阱
1. async void(別用)
// ❌ async void:例外無法被 catch
async void BadMethod() { throw new Exception(); } // 直接崩潰
// ✅ async Task:例外會被 Task 包住
async Task GoodMethod() { throw new Exception(); } // 可以被 await catch
2. .Result / .Wait()(別用)
// ❌ 同步等待非同步 → 可能死鎖
var data = GetDataAsync().Result; // ASP.NET 中會死鎖!
// ✅ 一路 await 到底
var data = await GetDataAsync();
3. 不需要 async 的 async
// ❌ 多餘的 async(只是轉傳)
async Task<User> GetUser(int id) {
return await _repo.GetAsync(id); // 多一層狀態機,沒意義
}
// ✅ 直接回傳 Task
Task<User> GetUser(int id) {
return _repo.GetAsync(id); // 少一層包裝,效能更好
}
執行緒 vs Task
Thread:作業系統層級,建立成本高(~1MB stack)
Task:CLR 層級,使用 ThreadPool,輕量
async/await:語法糖,編譯器轉成狀態機(State Machine)
await 不等於開新 Thread。await 只是告訴編譯器:「這裡可以暫停,等結果回來再繼續。」