CancellationTokenSource cts
是用于控制任務(線程)停止運行的。我們一步步來解釋它的作用。
🔍 現在的代碼結構大概是這樣的:
Task.Run(() => {while (true){// 不斷循環采集圖像}
});
這種寫法雖然簡單,但最大的問題是:
- 這個線程永遠停不下來。
- 你無法在外部優雅地告訴它“嘿,停一下”,除非強行殺掉進程。
? 而 CancellationTokenSource
就是為了解決這個問題。
📌 它的作用:
提供一種機制,可以從外部請求取消正在運行的任務或線程。
🧠 舉個簡單的例子
CancellationTokenSource cts = new CancellationTokenSource();Task.Run(() =>
{while (!cts.Token.IsCancellationRequested){Console.WriteLine("工作中...");Thread.Sleep(1000);}Console.WriteLine("收到取消信號,退出循環!");
});// 5秒后取消
Thread.Sleep(5000);
cts.Cancel();
輸出:
工作中...
工作中...
工作中...
工作中...
工作中...
收到取消信號,退出循環!
🚦 實際在你的程序中,使用它的意義是:
- 將來你關閉程序或點擊“停止”按鈕時,
- 你只需調用
cts.Cancel()
, - 運行中的
while (!cts.Token.IsCancellationRequested)
就會安全退出。
🔁 沒有它會發生什么?
如果你一直用 while (true)
,即使界面關閉、用戶點擊“停止”,后臺線程仍然在跑,非常浪費資源,甚至會造成程序無法正常退出或 CPU 飆高。
? 總結一句話:
CancellationTokenSource
就像一個“遙控開關”,可以讓后臺線程知道“該停下來了”。
它和while中放個bool變量有啥區別
“在 while
中放一個 bool
變量”確實也是一種控制循環退出的方式,那它和 CancellationTokenSource
有什么區別呢?我們來做個對比👇
🆚 bool
控制變量 vs CancellationToken
對比項 | bool 控制變量 | CancellationTokenSource |
---|---|---|
? 基本功能 | 控制循環停止 | 控制循環停止 |
👨?💻 用法簡單 | 非常簡單(比如 while (running) ) | 稍復雜,需要 cts.Token 等對象 |
? 多任務控制 | 需要共享變量或加鎖 | 天生就支持多個任務共享同一個 token |
? 線程安全性 | 需要你手動保證線程安全(如加 volatile 或 lock ) | 自帶線程安全,不容易出錯 |
🔄 與 Task / async / Parallel 等配合 | 兼容差,不好配合現代并發工具 | 與 Task.Run 、Parallel.ForEach 、async/await 等無縫集成 |
💣 取消等待 | 無法中斷 Task.Delay / Wait 等 | 可中斷等待任務,比如 Task.Delay(..., token) |
🎯 最佳應用場景 | 簡單循環退出 | 復雜并發任務、任務取消、超時控制等 |
🔍 示例對比:
? 用 bool
控制變量:
volatile bool running = true;Task.Run(() =>
{while (running){// 采圖}
});// 關閉時
running = false;
注意這里用了 volatile
是因為如果不寫,可能在多線程中有緩存失效的問題(CPU 緩存沒及時刷新變量值)。
? 用 CancellationToken
:
CancellationTokenSource cts = new CancellationTokenSource();Task.Run(() =>
{while (!cts.Token.IsCancellationRequested){// 采圖}
});// 關閉時
cts.Cancel();
這樣可以安全、優雅地中止線程,還可以用于 Task.Delay
、Parallel.ForEach
、HttpClient.SendAsync
等現代 API。
深入探討
逐條舉例 + 拆解對比,讓它變得通俗易懂且能立刻理解!
? 多任務控制(多線程共享控制)
🔸 bool
的寫法:
你得寫成這樣👇
volatile bool _running = true;Task.Run(() =>
{while (_running){// 做事...}
});Task.Run(() =>
{while (_running){// 另一個線程做事...}
});
- 如果多個任務要響應這個
bool
,你得自己保證它是線程安全的(加volatile
), - 若再多幾個線程,管理起來越來越麻煩。
? CancellationToken
的寫法(天然支持):
var cts = new CancellationTokenSource();
var token = cts.Token;Task.Run(() => Work(token));
Task.Run(() => Work(token));void Work(CancellationToken token)
{while (!token.IsCancellationRequested){// 干活...}
}
? 所有任務共用一個 token,控制統一、安全、優雅,代碼看起來也很干凈。
? 線程安全性
🔸 bool
寫法:
必須加 volatile
或 lock
,否則有可能一個線程改了,另一個線程根本“看不見”:
volatile bool _running = true;// 不加 volatile,可能一個線程死循環都不會停
或者你得用 lock
,比如:
lock (_lockObj)
{_running = false;
}
復雜且容易忘。
? CancellationToken
- 內部已經做好線程安全,不用你操心
.IsCancellationRequested
是線程安全的讀操作.Cancel()
會安全地廣播給所有監聽者
🔄 與 Task / async / Parallel 等配合
🔸 bool
寫法:
比如你想寫個 Task.Delay(5000)
,中途取消:
await Task.Delay(5000); // 沒法中斷!
你只能寫復雜邏輯輪詢 bool
,還不能打斷系統 API。
? CancellationToken
:
await Task.Delay(5000, token); // 支持取消!
同樣適用于:
Parallel.ForEach(..., token, ...)
HttpClient.SendAsync(..., token)
Task.Run(() => ..., token)
它和這些 現代異步/并發工具是“原生支持”的搭檔。
🧠 總結一句話:
bool
適合非常簡單的控制,一兩個線程內部用可以;但如果你要和 Task、多個線程、現代異步配合,CancellationToken
才是真正線程安全、優雅且可組合的現代解決方案。
? 建議
- 簡單項目、沒用多線程/異步的地方 →
bool
就夠了。 - 多線程、用
Task
、async/await
、任務并發的項目 → 建議用CancellationToken
,更穩更現代。
如果你目前只是用一個線程干活,可以繼續用 bool
,但隨著項目復雜度提升,CancellationToken
會更適合并發環境,特別是你這個多相機+高速采集的系統。以后維護、擴展也更舒服。
2025年3月29日---------------新增內容:
Task.Delay
的第二個參數不是 CancellationTokenSource
,而是它里面的 CancellationToken
。
? 正確語法:
var cts = new CancellationTokenSource();
await Task.Delay(5000, cts.Token); // 注意這里是 cts.Token,不是 cts 本身
?為什么不是 cts
本身?
CancellationTokenSource
是控制器:可以.Cancel()
來通知取消。CancellationToken
是“令牌”:傳給任務或方法來監聽是否被取消。
// 你控制取消:
cts.Cancel();// 你監聽是否被取消:
token.IsCancellationRequested
就像你開會:
CancellationTokenSource
是主持人(可以宣布“散會!”)CancellationToken
是給每個人的耳機(聽到“散會”指令)
🚫 錯誤寫法(會編譯錯誤):
await Task.Delay(5000, cts); // ? 錯!類型不匹配
? 正確寫法:
await Task.Delay(5000, cts.Token); // ?
cts.Cancel(); 之后,下次再次啟動怎么辦?
在調用 cts.Cancel();
之后,CancellationTokenSource
不能被重用。如果你想要再次啟動任務,需要創建一個新的 CancellationTokenSource
實例。
? 不能直接復用 CancellationTokenSource
cts.Cancel();
調用后,cts.Token
狀態不會被重置,即使你重新啟動任務,它會立即認為任務已被取消。
? 解決方案:創建新的 CancellationTokenSource
每次啟動新任務時,都需要創建一個新的 CancellationTokenSource
。
CancellationTokenSource cts = new CancellationTokenSource();void StartTask()
{cts = new CancellationTokenSource(); // 重新創建一個新的 ctsTask.Run(() =>{while (!cts.Token.IsCancellationRequested){Console.WriteLine("運行中...");Thread.Sleep(1000);}Console.WriteLine("循環退出");});
}// 啟動任務
StartTask();// 運行 3 秒后取消
Thread.Sleep(3000);
cts.Cancel();
Console.WriteLine("任務已停止");// 等待一會兒,再次啟動
Thread.Sleep(2000);
StartTask(); // ? 重新創建 cts,再次啟動任務
Console.WriteLine("任務重新啟動");
🔹 正確做法:
- 每次
StartTask()
都創建一個新的CancellationTokenSource
。 - 這樣
cts.Token.IsCancellationRequested
重新變為false
,新任務不會立即退出。