162

CancellationToken我正在寻找除了CancellationTokenSource类之外还引入.NET 结构的理由。我了解如何使用 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,并且对于每个令牌,取消检查都是本地字段访问?

鉴于在这两种情况下没有锁定的 volatile bool 就足够了,我仍然不明白为什么会更快。

谢谢!

4

5 回答 5

140

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

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

如果对行为不端的 API 敞开大门,那么取消设计的实用性可能会很快被削弱。

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

CancellationToken== "取消监听器"

于 2013-03-28T00:40:44.373 回答
102

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

接受的答案完全正确。这是设计此功能的团队的确认(强调我的):

两种新类型构成了框架的基础: ACancellationToken是一个结构,表示“潜在的取消请求”。此结构作为参数传递给方法调用,方法可以对其进行轮询或注册回调以在请求取消时触发。ACancellationTokenSource是一个类,它提供了用于发起取消请求的机制,并且它具有Token 用于获取关联令牌的属性。将这两个类合并为一个是很自然的,但这种设计允许将两个关键操作(发起取消请求与观察和响应取消)完全分开。特别是,只接受 a 的方法CancellationToken可以观察到取消请求,但不能发起取消请求。

链接:.NET 4 取消框架

在我看来,CancellationToken只能观察状态而不能改变状态这一事实是非常关键的。您可以像分发糖果一样分发代币,而不必担心除您之外的其他人会取消它。它可以保护您免受恶意第三方代码的侵害。是的,机会很小,但我个人喜欢这种保证。

我也觉得它使 API 更干净,避免意外错误,促进更好的组件设计。

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

取消令牌 API

CancellationTokenSource API

如果你将它们结合起来,在编写 LongRunningFunction 时,我会看到那些我不应该使用的方法,比如“取消”的多个重载。就个人而言,我也讨厌看到 Dispose 方法。

我认为当前的类设计遵循“成功之坑”的理念,它指导开发人员创建可以处理Task取消的更好的组件,然后以多种方式将它们组合在一起以创建复杂的工作流程。

我问你一个问题,你有没有想过token.Register的目的是什么?这对我来说没有意义。然后我阅读了托管线程中的取消,一切都变得一清二楚。

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

于 2013-07-11T16:26:36.763 回答
65

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

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

于 2013-01-08T13:28:23.240 回答
10

CancellationToken是一个结构,由于将其传递给方法,因此可能存在许多副本。

调用源时设置令牌的CancellationTokenSource所有副本的状态。请参阅此 MSDN 页面Cancel

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

于 2013-01-08T13:26:28.617 回答
2

无论出于何种原因,它都是发出取消的CancellationTokenSource“事物”。它需要一种方法来将取消“发送”到CancellationToken它已发出的所有 '。例如,当请求中止时,ASP.NET 就是这样取消操作的。每个请求都有一个CancellationTokenSource将取消转发给它已发布的所有令牌。

这非常适合单元测试顺便说一句 - 创建您自己的取消令牌源,获取令牌,调用Cancel源,并将令牌传递给您必须处理取消的代码。

于 2013-01-08T13:30:39.787 回答