在C#异步开发中,使用CancellationToken取消任务时,经常会遇到TaskCanceledException和OperationCanceledException。理解它们的产生机制和正确处理方式,对编写健壮的异步程序非常重要。
两者的关系与区别
OperationCanceledException是所有取消操作的基类异常。TaskCanceledException继承自TaskCanceledException的父类OperationCanceledException,专门表示某个Task被取消。当调用CancellationToken.ThrowIfCancellationRequested时,抛出的是OperationCanceledException;而当Task被取消且等待该Task时,运行时通常包装为TaskCanceledException。
典型触发场景
- 手动调用token.ThrowIfCancellationRequested()会抛出OperationCanceledException
- 使用Task.Delay(毫秒, token)并在延迟前取消,等待结果会收到TaskCanceledException
- Task.Run中传入已取消的token,返回的任务状态为Canceled,await时抛TaskCanceledException
正确处理方式
由于TaskCanceledException是OperationCanceledException的子类,捕获时应先捕获具体的TaskCanceledException,再捕获基类,避免掩盖其他取消来源。
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var cts = new CancellationTokenSource();
cts.Cancel(); // 提前取消
try
{
await DoWorkAsync(cts.Token);
}
catch (TaskCanceledException ex)
{
// 处理任务取消
Console.WriteLine("任务被取消: " + ex.Message);
}
catch (OperationCanceledException ex)
{
// 处理更通用的取消
Console.WriteLine("操作被取消: " + ex.Message);
}
}
static async Task DoWorkAsync(CancellationToken token)
{
// 模拟可取消的异步工作
await Task.Delay(1000, token);
}
}
在多个任务中处理取消
当使用Task.WhenAll等待多个任务时,只要有一个任务取消,整体await就会抛出TaskCanceledException。可以通过检查单个任务状态来区分。
using System;
using System.Threading;
using System.Threading.Tasks;
class Demo
{
static async Task RunAll()
{
var cts = new CancellationTokenSource();
Task t1 = Task.Delay(500, cts.Token);
Task t2 = Task.Delay(1000);
try
{
await Task.WhenAll(t1, t2);
}
catch (OperationCanceledException)
{
if (t1.IsCanceled)
Console.WriteLine("t1被取消");
}
}
}
常见错误
不要只捕获Exception而忽略取消异常,否则调用方无法知道操作是正常结束还是被取消。也不要在catch中重新抛出时不带token,导致上层无法识别取消来源。推荐在方法签名中接受CancellationToken,并在取消时让其自然抛出对应异常。
取消是协作式的,只有代码主动检查token并停止工作,取消才能生效。
总结建议
优先捕获TaskCanceledException,其次OperationCanceledException;使用IsCancellationRequested做轮询检查;在公共库方法中通过token抛出标准取消异常,可以让调用链更清晰可靠。
C#TaskCanceledExceptionOperationCanceledException修改时间:2026-07-29 19:51:46