CancellationToken与Abort
文章目录
- 1、什么是 CancellationToken
- 2、 核心作用:三大功能
- 2.1 协作式取消 - "礼貌的请求"
- 2.2 资源安全 - "优雅的退场"
- 2.3 传播机制 - "连锁反应"
- 3、底层原理
- 3.1 简单而高效的设计
- 3.2 工作流程
- 4、CancellationToken vs Thread.Abort:文明 vs 暴力
- 4.1 CancellationToken(文明的红绿灯)
- 4.2 Thread.Abort(暴力的拖车)
- 4.3 📊 详细对比表格
- 5、实际应用场景
- 5.1 场景1:用户界面响应
- 5.2 场景2:超时控制
- 5.3 场景3:多个取消条件
- 6、最佳实践
- 6.1 ✅ 正确做法
- 6.2 ❌ 错误做法
- 7、总结
1、什么是 CancellationToken
什么是 CancellationToken?—— “文明的红绿灯系统”
想象一下交通系统:
-
CancellationTokenSource = 交通指挥中心
-
CancellationToken = 司机看到的红绿灯
-
ThrowIfCancellationRequested() = 司机抬头看灯的动作
// 建立交通系统
var 指挥中心 = new CancellationTokenSource();
var 红绿灯 = 指挥中心.Token;// 司机开始行驶
var 送货任务 = Task.Run(() =>
{for (int 路口 = 0; 路口 < 100; 路口++){// 🚦 司机主动看红绿灯红绿灯.ThrowIfCancellationRequested();// 安全行驶到下一个路口开车到路口(路口);}
});// 指挥中心把绿灯变红灯
指挥中心.Cancel();
2、 核心作用:三大功能
2.1 协作式取消 - “礼貌的请求”
// 不是强制停止,而是礼貌地请求停止
var cts = new CancellationTokenSource();var worker = Task.Run(() =>
{while (!cts.Token.IsCancellationRequested) // 主动检查{// 工作...Console.WriteLine("工作中...");Thread.Sleep(1000);// 在合适的地方安全停止if (cts.Token.IsCancellationRequested){Console.WriteLine("收到停止请求,正在安全收尾...");// 清理资源、保存状态...break;}}
});// 3秒后礼貌地请求停止
await Task.Delay(3000);
cts.Cancel(); // 只是发出请求,不强制停止
2.2 资源安全 - “优雅的退场”
public async Task ProcessFileAsync(string filePath, CancellationToken ct)
{// 获取资源using var fileStream = File.OpenRead(filePath);using var reader = new StreamReader(fileStream);try{while (!reader.EndOfStream){ct.ThrowIfCancellationRequested(); // 检查点var line = await reader.ReadLineAsync();await ProcessLineAsync(line, ct); // 传递取消令牌ct.ThrowIfCancellationRequested(); // 再次检查}}catch (OperationCanceledException){// 🛑 安全清理:using语句会自动释放资源Console.WriteLine("取消时已自动释放文件资源");throw;}
}
2.3 传播机制 - “连锁反应”
// 一个取消信号,多个任务响应
var mainCts = new CancellationTokenSource();// 任务1:文件处理
var fileTask = ProcessFilesAsync(mainCts.Token);// 任务2:网络请求
var networkTask = FetchDataAsync(mainCts.Token);// 任务3:数据计算
var computeTask = CalculateAsync(mainCts.Token);// 一个取消信号,所有任务都会收到
await Task.Delay(2000);
mainCts.Cancel(); // 所有任务都会优雅停止try
{await Task.WhenAll(fileTask, networkTask, computeTask);
}
catch (OperationCanceledException)
{Console.WriteLine("所有任务已安全取消");
}
3、底层原理
3.1 简单而高效的设计
// 简化的底层结构
public class CancellationTokenSource
{private volatile bool _isCancellationRequested; // 🎯 核心:一个布尔标志private List<Action> _callbacks = new List<Action>(); // 回调列表public CancellationToken Token => new CancellationToken(this);public void Cancel(){_isCancellationRequested = true; // 1. 设置标志位foreach (var callback in _callbacks) // 2. 执行回调callback.Invoke();}
}public struct CancellationToken
{private CancellationTokenSource _source;public bool IsCancellationRequested => _source?._isCancellationRequested ?? false;public void ThrowIfCancellationRequested(){if (IsCancellationRequested) // 🎯 只是检查布尔值throw new OperationCanceledException();}
}
3.2 工作流程
1. 创建: CancellationTokenSource cts = new()↓
2. 传递: 将 cts.Token 传递给任务↓
3. 检查: 任务中调用 token.ThrowIfCancellationRequested()↓
4. 取消: 外部调用 cts.Cancel()↓
5. 响应: 下次检查时任务抛出 OperationCanceledException↓
6. 清理: 在catch块中安全释放资源
4、CancellationToken vs Thread.Abort:文明 vs 暴力
4.1 CancellationToken(文明的红绿灯)
优点
// ✅ 安全可控
var cts = new CancellationTokenSource();
try
{await LongRunningOperation(cts.Token);
}
catch (OperationCanceledException)
{// 知道操作在安全状态下停止
}// ✅ 资源安全
using (var resource = AcquireResource())
{while (!ct.IsCancellationRequested){// 工作...}// using块确保资源被释放
}// ✅ 可预测性
// 你知道取消会在哪个检查点发生
4.2 Thread.Abort(暴力的拖车)
缺点:
// ❌ 危险!
var thread = new Thread(() =>
{using (var file = File.OpenWrite("data.dat")){for (int i = 0; i < 1000000; i++){// 如果在这里被Abort,文件可能损坏!WriteData(file, i);}}
});thread.Start();
thread.Abort(); // 💥 可能破坏文件数据!// ❌ 资源泄漏
// 可能无法执行finally块和Dispose()
4.3 📊 详细对比表格
特性 | CancellationToken | Thread.Abort |
---|---|---|
控制方式 | 协作式 - 任务主动配合 | 强制式 - 外部强行中断 |
安全性 | ⭐⭐⭐⭐⭐ 非常安全 | ⭐ 非常危险 |
资源管理 | ✅ 确保资源清理 | ❌ 可能泄漏资源 |
可预测性 | ✅ 知道停止位置 | ❌ 任意位置停止 |
数据一致性 | ✅ 保持数据完整 | ❌ 可能破坏数据 |
.NET Core | ✅ 完全支持 | ❌ 已移除 |
使用场景 | 现代异步编程 | 遗留代码(避免使用) |
5、实际应用场景
5.1 场景1:用户界面响应
// 用户点击"取消"按钮时停止操作
private CancellationTokenSource _searchCts;private async void SearchButton_Click(object sender, EventArgs e)
{// 取消之前的搜索_searchCts?.Cancel();_searchCts = new CancellationTokenSource();try{await SearchAsync(searchTextBox.Text, _searchCts.Token);statusLabel.Text = "搜索完成";}catch (OperationCanceledException){statusLabel.Text = "搜索已取消";}
}private void CancelButton_Click(object sender, EventArgs e)
{_searchCts?.Cancel(); // 用户点击取消
}
5.2 场景2:超时控制
// 5秒超时自动取消
public async Task<T> ExecuteWithTimeoutAsync<T>(Func<CancellationToken, Task<T>> operation)
{using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));try{return await operation(cts.Token);}catch (OperationCanceledException) when (cts.Token.IsCancellationRequested){throw new TimeoutException("操作超时");}
}// 使用
var result = await ExecuteWithTimeoutAsync(async ct =>
{await Task.Delay(10000, ct); // 模拟长时间操作return "结果";
});
5.3 场景3:多个取消条件
// 组合多个取消条件
public async Task ProcessDataAsync(CancellationToken userToken = default)
{// 超时取消:最多运行30秒using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));// 用户取消 + 超时取消using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(userToken, timeoutCts.Token);await DoWorkAsync(linkedCts.Token);
}
6、最佳实践
6.1 ✅ 正确做法
// 1. 及时检查取消
public async Task LongOperationAsync(CancellationToken ct)
{for (int i = 0; i < 100; i++){ct.ThrowIfCancellationRequested(); // 频繁检查await ProcessItemAsync(i, ct);}
}// 2. 传递取消令牌
public async Task ProcessItemAsync(int id, CancellationToken ct)
{// 将取消令牌传递给所有异步操作await httpClient.GetAsync(url, ct);await fileStream.ReadAsync(buffer, 0, buffer.Length, ct);
}// 3. 合理使用using
public async Task ProcessAsync(CancellationToken ct)
{using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);cts.CancelAfter(TimeSpan.FromMinutes(5)); // 附加超时await DoWorkAsync(cts.Token);
}
6.2 ❌ 错误做法
// 1. 忽略取消令牌
public async Task BadMethodAsync(CancellationToken ct) // ❌ 参数不用
{// 从不检查取消,调用者无法取消这个操作await Task.Delay(10000);
}// 2. 不及时检查
public void LongRunningCalculation(CancellationToken ct)
{for (int i = 0; i < 1000000; i++){// 每100万次迭代才检查一次!❌if (i % 1000000 == 0)ct.ThrowIfCancellationRequested();HeavyCalculation(i);}
}// 3. 不传递取消令牌
public async Task<string> DownloadAsync(CancellationToken ct)
{// ❌ 没有传递取消令牌,网络请求无法取消return await httpClient.GetStringAsync("http://example.com");
}
7、总结
CancellationToken 是现代C#异步编程的基石:
🎯 核心优势:
-
安全第一:不会破坏数据或泄漏资源
-
协作友好:任务可以优雅地结束工作
-
灵活传播:一个取消信号可以传递给多个操作
-
性能高效:底层只是简单的标志位检查
🚫 Thread.Abort 已被淘汰的原因:
-
像"拔电源"一样危险
-
可能在任何代码位置中断,导致状态不一致
-
.NET Core/5+ 中已不再支持
用 CancellationToken 就像用红绿灯 - 安全、可预测、文明
用 Thread.Abort 就像强行拖车 - 危险、破坏性、已过时
在现代C#开发中,始终使用 CancellationToken 来管理异步操作的取消,这是编写健壮、可靠应用程序的关键!