.NET 早期的異步編程模型 (APM) —— BeginInvoke/EndInvoke、IAsyncResult、AsyncCallback —— 功能強大,但缺少標準化的超時/取消機制。開發者常需自行管理 Thread.Abort (不安全) 或輪詢標誌位。本文介紹 CancellableTask 類,在保持 APM 風格的前提下,提供可配置超時與自定義取消回調的擴展。
BeginInvoke(arg, callback, state, timeoutMs)、EndInvoke、IAsyncResult 完全兼容。Thread.Abort (兼容舊代碼) + 支援自定義 CancelCallback (協作式取消,安全)。EndInvoke 拋出;超時中止拋出 ThreadAbortException。
var task = new CancellableTask(Work);
var arg = new State { Loop = 20, Stop = false };
IAsyncResult ar = task.BeginInvoke(arg, ar => Console.WriteLine("Callback"), null, 10_000); // 10秒超時
ar.AsyncWaitHandle.WaitOne();
try {
object result = task.EndInvoke(ar);
Console.WriteLine("Result: " + result);
} catch (ThreadAbortException) { Console.WriteLine("Thread Aborted"); }
catch (Exception ex) { Console.WriteLine(ex); }
var task = new CancellableTask(Work, Cancel); // 傳入取消回調
var arg = new State { Loop = 20, Stop = false };
task.BeginInvoke(arg, ar => {
try { Console.WriteLine("Result: " + task.EndInvoke(ar)); }
catch (ThreadAbortException) { Console.WriteLine("Aborted"); }
catch (Exception ex) { Console.WriteLine(ex); }
}, arg, 10_000);
// 取消邏輯:設置標誌位,Work 內部輪詢
static void Cancel(object state) => ((State)state).Stop = true;
static object Work(object arg) {
var s = (State)arg;
for (int i = 0; i < s.Loop; i++) {
if (s.Stop) break; // 協作式退出
Console.WriteLine(i); Thread.Sleep(1000);
}
return i;
}
public IAsyncResult BeginInvoke(object arg, AsyncCallback callback, object state, int timeout)
{
wrapper = delegate(object argv) {
var e = new AutoResetEvent(false);
try {
var waitState = new TimeoutState(Thread.CurrentThread, state);
// 註冊等待:timeout 到期觸發 WaitOrTimeout
ThreadPool.RegisterWaitForSingleObject(e, WaitOrTimeout, waitState, timeout, true);
return workCallback(argv); // 執行真實工作
} finally { e.Set(); } // 工作提前結束,觸發已註冊等待的非超時分支
};
return wrapper.BeginInvoke(arg, callback, state); // 使用標準委托 APM
}
protected void WaitOrTimeout(object state, bool isTimeout)
{
if (isTimeout) {
var ts = (TimeoutState)state;
if (cancelCallback != null) cancelCallback(ts.state); // 自定義取消
else ts.thread.Abort(); // 預設強制中止
}
// 非超時 (isTimeout=false) 什麼都不做,工作正常完成
}
public class CancellableTask {
public delegate object WorkCallback(object arg);
public delegate void CancelCallback(object state);
protected class TimeoutState { internal Thread thread; internal object state; ... }
protected WorkCallback workCallback;
protected CancelCallback cancelCallback;
protected WorkCallback wrapper; // 包裝後的委托
public CancellableTask(WorkCallback work) { this.workCallback = work; }
public CancellableTask(WorkCallback work, CancelCallback cancel) : this(work) { this.cancelCallback = cancel; }
public IAsyncResult BeginInvoke(object arg, AsyncCallback callback, object state, int timeout) { ... }
public object EndInvoke(IAsyncResult result) => wrapper.EndInvoke(result);
protected void WaitOrTimeout(object state, bool isTimeout) { ... }
}
| 技術點 | 說明 |
|---|---|
ThreadPool.RegisterWaitForSingleObject | 高效的等待註冊,由執行緒池監控 WaitHandle,超時在執行緒池執行緒上觸發回調,無需額外計時器執行緒。 |
AutoResetEvent 作為觸發器 | 工作正常結束時 e.Set(),喚醒等待器,isTimeout=false;超時則由執行緒池直接觸發 isTimeout=true。 |
| Wrapper 委托捕獲 | wrapper 字段保存包裝委托,供 EndInvoke 調用,保證 APM 鏈路完整。 |
| 異常處理 | 工作異常在 EndInvoke 重拋;Thread.Abort 產生 ThreadAbortException 同樣在 EndInvoke 拋出;取消回調異常被吞噬 (避免崩潰監控執行緒)。 |
⚠️ Thread.Abort 危險:可能導致鎖未釋放、非托管資源洩漏、應用程式域不穩定。生產環境強制要求提供 CancelCallback 實現協作式取消。
static void SafeWork(object arg) {
var state = (MyState)arg;
for (int i = 0; i < state.MaxIterations; i++) {
if (state.CancellationToken.IsCancellationRequested) break; // 或輪詢 bool Stop
// 執行可中斷的小步驟
DoStep(i);
}
}
static void SafeCancel(object state) => ((MyState)state).CancellationToken.Cancel(); // 或 state.Stop = true;
| 特性 | CancellableTask (APM) | CancellationToken (TAP) |
|---|---|---|
| 適用模型 | 委托 APM (Begin/EndInvoke) | Task/async-await (TAP) |
| 取消機制 | 超時自動觸發 + 可選回調 | 統一 CancellationTokenSource/Token |
| 協作式取消 | 手動輪詢標誌位/Token | 原生支援 ThrowIfCancellationRequested |
| 組合性 | 差 (單獨任務) | 優 (Token.Register、LinkedTokenSource) |
| 推薦場景 | 維護舊 APM 代碼、無法升級 TAP | 所有新代碼、.NET 4.5+ |
CancellableTask 為遺留 APM 代碼提供了實用的超時擴展,核心利用 ThreadPool.RegisterWaitForSingleObject 實現高效計時監控。但新專案應直接採用 TAP (Task/async-await) + CancellationToken,享受統一、可組合、類型安全的現代取消模型。本方案僅作為存量系統平滑遷移的過渡工具。