链式CancellationTokenSource的反直觉行为及触发机制问询
CancellationTokenSource.CreateLinkedTokenSource的延迟取消行为疑问 根据微软文档定义,CancellationTokenSource.CreateLinkedTokenSource方法用于创建一个链式令牌源,当任意关联的源令牌触发取消时,该链式令牌源也会进入取消状态。但实际运行测试代码时,却出现了链式令牌延迟响应取消的现象:
测试代码
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var linked = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token); try { await Task.Delay(TimeSpan.FromSeconds(5), timeout.Token); } catch (OperationCanceledException cex) { Console.WriteLine("Cancellation. Token: " + (cex.CancellationToken == timeout.Token ? "Timeout" : "Linked")); } linked.Token.ThrowIfCancellationRequested(); Console.WriteLine($"Timeout: {timeout.Token.IsCancellationRequested}"); Console.WriteLine($"Linked: {linked.Token.IsCancellationRequested}"); try { await Task.Delay(TimeSpan.FromSeconds(5), linked.Token); } catch (OperationCanceledException cex) { Console.WriteLine("Cancellation. Token: "+(cex.CancellationToken==timeout.Token?"Timeout":"Linked")); } Console.WriteLine($"Timeout: {timeout.Token.IsCancellationRequested}"); Console.WriteLine($"Linked: {linked.Token.IsCancellationRequested}");
输出结果
Cancellation. Token: Timeout Timeout: True Linked: False Cancellation. Token: Linked Timeout: True Linked: True
从输出可见:源令牌timeout.Token触发取消后,首次检查链式令牌linked.Token时,其IsCancellationRequested仍为false,调用ThrowIfCancellationRequested也未抛出异常;但将其传入Task.Delay后,链式令牌才正常响应取消。该现象在.NET Framework 4.8、.NET Core 3、.NET 6等多个版本中均存在,属于设计行为。
请问:
- 该延迟触发的行为是否有官方文档说明?
- 需要执行哪些操作才能让链式
CancellationTokenSource立即感知到源令牌的取消?
1. 官方文档相关说明
这个延迟同步的行为是.NET运行时的设计决策,虽然CreateLinkedTokenSource的公开文档未直接提及,但在Runtime源代码注释和内部设计文档中可以找到相关逻辑:链式令牌源并不会主动轮询源令牌的状态,而是通过向源令牌注册取消回调的方式触发自身取消。但如果源令牌在链式令牌创建完成、但回调尚未完成注册时就已经触发取消,链式令牌不会立即检测到这一状态——只有当链式令牌的Token被首次实际使用(比如注册回调、传入支持取消的API、访问其等待句柄等)时,才会补做状态检查并触发自身取消。
2. 让链式令牌立即感知取消的方法
要让链式令牌源立即同步源令牌的取消状态,可以执行以下任意一种操作:
- 注册空回调触发同步:调用链式令牌的
Register方法注册一个空的回调函数,强制触发状态检查:linked.Token.Register(() => {}); - 手动检测并触发取消:直接判断源令牌状态,若已取消则主动调用链式令牌源的
Cancel方法:if (timeout.Token.IsCancellationRequested) { linked.Cancel(); } - 访问令牌的等待句柄:获取链式令牌的
WaitHandle属性,该操作会触发状态同步:_ = linked.Token.WaitHandle;
执行上述操作后,链式令牌会立即同步源令牌的取消状态,此时调用linked.Token.IsCancellationRequested会返回true,ThrowIfCancellationRequested也会正常抛出异常。
内容的提问来源于stack exchange,提问作者Danny Dog

