直接抛出OperationCanceledException为何会取消Task?
问题描述
我查阅微软文档得知,将Task状态设为Canceled需满足三个条件:
- 抛出OperationCanceledException(或其派生类如TaskCanceledException);
- token.IsCancellationRequested为true;
- 传入OperationCanceledException的token与创建Task时传入的token一致。
但如下示例未传入任何token直接抛出异常却成功取消了Task,我猜测是因为token是结构体,默认值满足后两个条件,但不确定,希望得到解释。
var task = Task.Run(() => { throw new OperationCanceledException(); }); try { task.Wait(); } catch (AggregateException) { Console.WriteLine(task.Status); // Cancelled }
解释说明
你的猜测方向是对的,核心原因和CancellationToken是值类型的默认实例有关,具体拆解如下:
Task关联的默认Token
你使用的Task.Run(Action)重载没有传入自定义CancellationToken,此时Task内部关联的是CancellationToken.None——也就是值类型CancellationToken的默认实例,它永远不会触发取消请求(IsCancellationRequested始终为false)。异常的默认Token
你抛出的new OperationCanceledException()没有指定token参数,这个异常的CancellationToken属性同样是CancellationToken.None,和Task关联的Token完全一致。特殊规则适配
微软文档里的三个条件是针对带有用户自定义、可触发取消的CancellationToken的场景。而当Task关联的是CancellationToken.None时,规则会简化:只要抛出的OperationCanceledException携带的Token和Task关联的Token一致(这里都是None),Task就会被标记为Canceled状态,不需要满足token.IsCancellationRequested为true——因为None Token本身就不存在“被请求取消”的情况,此时抛出该异常被视为主动取消Task的行为。
综上,你的示例中Task和异常的Token都是默认的None,满足匹配条件,因此Task会进入Canceled状态。
内容的提问来源于stack exchange,提问作者jamgoo

