为什么取消令牌与取消令牌源是分开的?

我在找理由。除了 CancellationTokenSource类之外,还引入了 NET CancellationToken结构。我了解 怎么做的 API 是要使用的,但也想了解 为什么是这样设计的。

也就是说,为什么我们有:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);


...
public void SomeCancellableOperation(CancellationToken token) {
...
token.ThrowIfCancellationRequested();
...
}

而不是像这样直接传递 CancellationTokenSource:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);


...
public void SomeCancellableOperation(CancellationTokenSource cts) {
...
cts.ThrowIfCancellationRequested();
...
}

这是基于取消状态检查比传递令牌更频繁这一事实的性能优化吗?

这样 CancellationTokenSource就可以跟踪和更新 CancellationTokens,而对于每个令牌,取消检查是一个本地字段访问?

考虑到在这两种情况下没有锁定的易失性 bool 就足够了,我仍然不明白为什么这样做会更快。

谢谢!

26774 次浏览

CancellationToken是一个结构体,因为将它传递给方法,所以可能存在许多副本。

在源上调用 Cancel时,CancellationTokenSource设置令牌的所有副本的状态

设计的原因可能只是关注点分离和结构的速度。

它们之所以分开,不是因为技术原因,而是因为语义原因。如果查看 ILSpy 下 CancellationToken的实现,您会发现它只是 CancellationTokenSource的一个包装器(因此在性能方面与传递引用没有什么不同)。

它们提供了这种功能分离,使事情更可预测: 当您传递一个 CancellationToken方法时,您知道您仍然是唯一可以取消它的方法。当然,该方法仍然可以抛出一个 TaskCancelledException,但是 CancellationToken本身——以及引用相同标记的任何其他方法——将保持安全。

CancellationTokenSource是发出取消命令的“东西”,不管出于什么原因。它需要一种方式来“分派”,取消所有的 CancellationToken的它已经发出。例如,ASP.NET 就是这样在请求中止时取消操作的。每个请求都有一个 CancellationTokenSource,它将取消转发给它发出的所有令牌。

这对于单元测试来说非常棒——创建您自己的取消令牌源,获得一个令牌,在源上调用 Cancel,并将令牌传递给必须处理取消的代码。

我参与了这些类的设计和实现。

简短的回答是“ 关注点分离”。的确,有各种各样的实施战略,有些战略至少在类型系统和初步学习方面更简单。然而,CTS 和 CT 适用于许多场景(如深库堆栈、并行计算、异步等) ,因此在设计时考虑了许多复杂的用例。这种设计旨在鼓励成功的模式,并在不牺牲性能的情况下阻止反模式。

如果因为 API 的错误行为而敞开大门,那么取消设计的有用性可能很快就会受到侵蚀。

CancellationTokenSource = = “取消触发器”,加上生成链接侦听器

CancellationToken = = “取消侦听器”

我有一个确切的问题,我想了解这个设计背后的基本原理。

公认的答案完全正确。下面是设计这个特性的团队的确认(重点是我的) :

两种新类型构成了框架的基础: CancellationToken是 表示“可能的取消请求”的结构 Struct 作为参数传递到方法调用中,并且该方法可以 或注册一个回调,以便在取消时触发 CancellationTokenSource是一个类,它提供 启动取消请求的机制,它有一个 Token 属性来获取关联的令牌。这是很自然的 要将这两个类合并为一个类,< strong > 但是这个设计允许 键操作(初始化取消请求与观察和 (对取消作出反应)要清楚地分开, 只使用 CancellationToken的方法可以观察到取消 请求但不能启动

链接: .NET 4取消框架

在我看来,事实上 CancellationToken只能观察状态而不能改变它,这是非常关键的。你可以像发糖果一样发出代币,永远不用担心除了你之外的其他人会取消它。它保护你不受敌对第三方代码的伤害。是的,机会很渺茫,但我个人喜欢这种保证。

我也觉得它使 API 更清洁,避免了偶然的错误,促进了更好的组件设计。

让我们看看这两个类的公共 API。

CancellationToken API

CancellationTokenSource API

如果你将它们组合在一起,在写 LongRunningfunction 的时候,我会看到类似“ Cancel”的多重重载这样的方法,我不应该使用它们。个人而言,我也不喜欢看到处理方法。

我认为目前的类设计遵循“成功的陷阱”的哲学,它指导开发人员创建更好的组件,可以处理 Task取消,然后仪器他们在一起,以多种方式创建复杂的工作流程。

让我问你一个问题,你有没有想过记号的目的是什么。收银机?我不明白。然后我读了 托管线程中的取消,一切都变得非常清晰。

我相信 TPL 的取消框架设计绝对是一流的。