多執行緒應用中,前景 UI 執行緒結束時,背景工作執行緒若仍在運行,會導致進程無法正常退出,甚至造成資源洩漏(如記憶體資料未寫入磁碟)。.NET 提供多種機制,本文從淺入深解析四大模式優劣與適用場景。
var worker = new Thread(DoWork) { IsBackground = true };
worker.Start();
try { /* 長時間工作 */ }
catch (ThreadAbortException)
{
// 僅能在這裡做極簡單清理
Thread.ResetAbort(); // 甚至可阻止中止
}
.NET Framework 4.0 引入 CancellationTokenSource / CancellationToken,成為取消模式標準。
public class CancellationDemo
{
public static void Main()
{
var cts = new CancellationTokenSource();
var worker = new Thread(DoWork);
worker.Start(cts.Token);
Console.WriteLine("按 'c' 取消執行緒...");
while (Console.ReadKey().KeyChar != 'c') { }
cts.Cancel();
worker.Join();
}
static void DoWork(object state)
{
var token = (CancellationToken)state;
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("執行緒收到取消請求,正在清理資源...");
// 清理:寫入檔案、釋放鎖、關閉連線
break;
}
Thread.SpinWait(500000); // 模擬計算
Console.WriteLine("持續工作中...");
}
}
}
token.IsCancellationRequested。cts.Token 可傳遞給多個執行緒、非同步方法,實現群組取消。當執行緒因 Thread.Sleep、AutoResetEvent.WaitOne、Socket.Receive 等阻塞時,無法輪詢 Token。解決方案:利用 CancellationToken.WaitHandle 與業務事件同步等待。
public class BlockingCancellationDemo
{
private static int _sharedValue;
public static void Main()
{
var valueReady = new AutoResetEvent(false);
var cts = new CancellationTokenSource();
var state = new { ValueReady = valueReady, Token = cts.Token };
new Thread(Consumer).Start(state);
new Thread(Producer).Start(state);
Console.WriteLine("按 'c' 取消所有執行緒...");
while (Console.ReadKey().KeyChar != 'c') { }
cts.Cancel(); // 觸發 Token.WaitHandle
}
static void Producer(object state)
{
dynamic s = state;
var rand = new Random();
while (!s.Token.IsCancellationRequested)
{
_sharedValue = rand.Next();
Console.WriteLine($"\r\n[生產] 產生值: {_sharedValue}");
s.ValueReady.Set(); // 通知消費者
Thread.Sleep(500);
}
Console.WriteLine("[生產] 執行緒已取消。");
}
static void Consumer(object state)
{
dynamic s = state;
// 同時等待:資料就緒事件 或 取消事件
var handles = new WaitHandle[] { s.ValueReady, s.Token.WaitHandle };
while (true)
{
int index = WaitHandle.WaitAny(handles);
if (index == 0) // 資料就緒
{
Console.WriteLine($"[消費] 處理值: {_sharedValue}");
}
else if (index == 1) // 取消事件
{
Console.WriteLine("[消費] 收到取消信號,退出迴圈。");
break;
}
}
}
}
CancellationToken.WaitHandle 內部是一個 ManualResetEvent,cts.Cancel() 會將其置為 Signaled。WaitHandle.WaitAny 只要任一 Handle 觸發即返回,並回傳索引,讓程式碼判斷是業務事件還是取消事件。Semaphore、Mutex、Task 等同步原語。| 模式 | 優點 | 缺點 | 推薦度 |
|---|---|---|---|
| IsBackground | 零成本 | 資料遺失風險極高 | ⭐ 僅限可丟棄工作 |
| Thread.Abort | 即時生效 | 不可靠、危險、已過時 | ❌ 禁止使用 |
| CancellationToken 輪詢 | 標準、安全、可組合 | 需修改工作迴圈、無法中斷阻塞 | ⭐⭐⭐⭐⭐ 首選 |
| WaitHandle.WaitAny | 支援阻塞場景 | 程式碼稍複雜 | ⭐⭐⭐⭐ 阻塞場景必備 |
核心原則:協作大於強制。設計長時間運行的背景工作時,請從一開始就引入
CancellationToken,並妥善處理阻塞呼叫的取消邏輯,才能構建健壯、可維護的多執行緒應用。