2026-01-12

[C#] CancellationToken 一直都有寫,但專案其實從來沒有真的取消過

最近都再釐清一些觀念跟償還技術債,其實只要常常遇到程式要大量改寫成非同步化常常對專案都是

毀滅性的更改,也就是大量翻修,這裡面有一個很常被忽略的東西,處理的好才能夠把非同步化達到最大價值


這邊我列出幾點,我這邊常遇到的狀況也附上我錯誤的寫法,也提醒自己不要之後不要犯這些錯

1. 方法有 CancellationToken,但實際完全沒用

public async Task DoWorkAsync(CancellationToken ct) { // 錯誤:ct 傳進來了,但完全沒用 // await Task.Delay(5000); // 改成把 ct 傳進支援取消的 API await Task.Delay(5000, ct); } // 呼叫方式 using var cts = new CancellationTokenSource(); var task = DoWorkAsync(cts.Token); await Task.Delay(1000); cts.Cancel(); await task;

2. 用 bool 或旗標自己做取消,這我很常用真的是壞習慣

public async Task DoWorkAsync(CancellationToken ct) { // 寫法:自己做取消旗標,Framework 完全不知情 // while (!_cancel) // { // await Task.Delay(100); // } // 改成用 CancellationToken 控制流程 while (!ct.IsCancellationRequested) { await Task.Delay(100, ct); } } // 呼叫方式 using var cts = new CancellationTokenSource(); var task = DoWorkAsync(cts.Token); cts.Cancel(); await task;

3. 不用 OperationCanceledException,而是用大絕招的 catch all

public async Task DoWorkAsync(CancellationToken ct) { try { await Task.Delay(5000, ct); } // 錯誤:把取消吃掉,上層完全不知道 // catch // { // } // 改成允許取消正常往上傳 catch (OperationCanceledException) { throw; } } // 呼叫方式 try { await DoWorkAsync(ct); } catch (OperationCanceledException) { // 明確知道這是取消,不是錯誤 }

4. 用 Task.Run 包同步程式碼,明明底層就有提供非同步的作法

public async Task DoWorkAsync(CancellationToken ct) { // 錯誤:CancellationToken 無法中斷同步程式碼 // await Task.Run(() => // { // Thread.Sleep(5000); // }, ct); //改成流程本身就是可取消的非同步 API await Task.Delay(5000, ct); } // 呼叫方式 using var cts = new CancellationTokenSource(); var task = DoWorkAsync(cts.Token); cts.Cancel(); await task;

5. 不使用底層內建的取消直接無腦忽略,只為了編譯過

public async Task<IActionResult> Get(CancellationToken ct) { // 錯誤&#65306;直接忽略 RequestAborted // await DoWorkAsync(CancellationToken.None); // 改成使用 ASP.NET Core 提供的取消來源 await DoWorkAsync(ct); return Ok(); } // 呼叫方式 // Client 關閉頁面就中斷連線觸發 ct

來個小結論:

在 .NET 裡,CancellationToken 常常不是忽略就會為了方便編譯過就亂寫

只有當它被實際用在支援取消的 API 上、沒有被自行取代或自動執行

系統行為上才真的存在取消這件事,否則不論程式碼看起來多完整,結果都等同於沒有取消。

這邊也是提醒自己不要再亂處理 CancellationToken 除了寫範例以外 :P


--

The bug existed in all possible states. Until I ran the code.

如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...