我正在寻找为什么除了 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
,并且对于每个令牌,取消检查都是本地字段访问?
鉴于在这两种情况下没有锁定的 volatile bool 就足够了,我仍然不明白为什么会更快。
谢谢!
我参与了这些类的设计和实现。
简短的回答是“关注点分离”。确实存在各种实现策略,并且至少在类型系统和初始学习方面有些更简单。但是,CTS 和 CT 旨在用于许多场景(例如深度库堆栈、并行计算、异步等),因此在设计时考虑了许多复杂的用例。这是一种旨在鼓励成功模式并在不牺牲性能的情况下阻止反模式的设计。
如果对行为不端的 API 敞开大门,那么取消设计的实用性可能会很快被削弱。
CancellationTokenSource
== "取消触发器",加上生成链接的侦听器
CancellationToken
== "取消监听器"
我有一个确切的问题,我想了解这个设计背后的基本原理。
接受的答案完全正确。这是设计此功能的团队的确认(强调我的):
两种新类型构成了框架的基础: CancellationToken 是一个结构,表示“潜在的取消请求”。此结构作为参数传递给方法调用,方法可以对其进行轮询或注册回调以在请求取消时触发。 CancellationTokenSource 是一个类,它提供了用于发起取消请求的机制,并且它具有用于获取关联令牌的 Token 属性。将这两个类合并为一个是很自然的,但这种设计允许将两个关键操作(发起取消请求与观察和响应取消)完全分开。特别是,仅采用 CancellationToken 的方法可以观察到取消请求,但不能发起取消请求。
链接:.NET 4 Cancellation Framework
在我看来,CancellationToken
只能观察状态而不能改变状态这一事实非常关键。您可以像分发糖果一样分发代币,而不必担心除您之外的其他人会取消它。它可以保护您免受恶意第三方代码的侵害。是的,机会很小,但我个人喜欢这种保证。
我也觉得它使 API 更干净,避免意外错误,促进更好的组件设计。
让我们看看这两个类的公共 API。
https://i.stack.imgur.com/J8xDX.png
https://i.stack.imgur.com/S3tsN.png
如果你将它们结合起来,在编写 LongRunningFunction 时,我会看到那些我不应该使用的方法,比如“取消”的多个重载。就个人而言,我也讨厌看到 Dispose 方法。
我认为当前的类设计遵循“成功之坑”的理念,它指导开发人员创建可以处理 Task
取消的更好的组件,然后以多种方式将它们组合在一起以创建复杂的工作流程。
我问你一个问题,你有没有想过token.Register的目的是什么?这对我来说没有意义。然后我读了Cancellation in Managed Threads,一切都变得一清二楚。
我相信 TPL 中的取消框架设计绝对是一流的。
CancellationTokenSource
如何在其关联的令牌上实际发起取消请求(令牌不能自己完成): CancellationToken 有这个内部构造函数:internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }
和这个属性:public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }
CancellationTokenSource 使用内部构造函数,因此令牌具有对源 (m_source) 的引用
它们是分开的,不是出于技术原因,而是出于语义原因。如果您查看 ILSpy 下 CancellationToken
的实现,您会发现它只是 CancellationTokenSource
的包装器(因此在性能方面与传递引用没有什么不同)。
它们提供了这种功能分离以使事情更可预测:当您将方法传递给 CancellationToken
时,您知道您仍然是唯一可以取消它的人。当然,该方法仍然可以抛出 TaskCancelledException
,但 CancellationToken
本身 - 以及引用相同标记的任何其他方法 - 将保持安全。
CancellationTokenSource
。你会认为你可以说“不要那样做”,但是人们(包括我!)偶尔会做这些事情来获得一些隐藏的功能,而且它会发生。这是我目前的理论,至少。
CancellationToken
是一个结构,由于将其传递给方法,因此可能存在许多副本。
当在源上调用 Cancel
时,CancellationTokenSource
设置令牌的所有副本的状态。 See this MSDN page
设计的原因可能只是关注点分离和结构速度的问题。
无论出于何种原因,CancellationTokenSource
都是发出取消的“事物”。它需要一种将取消“发送”到它已发出的所有 CancellationToken
的方法。例如,当请求中止时,ASP.NET 就是这样取消操作的。每个请求都有一个 CancellationTokenSource
,它将取消转发给它发出的所有令牌。
这非常适合单元测试顺便说一句 - 创建您自己的取消令牌源,获取令牌,在源上调用 Cancel
,然后将令牌传递给您必须处理取消的代码。
不定期副业成功案例分享
CancellationTrigger
和CancellationListener
是更好的名称。我想知道是否有办法改变这样的事情。