为什么CancellationToken与CancellationTokenSource分开?

我正在寻找为什么除CancellationTokenSource类之外还引入了.NET CancellationToken结构的基本原理。 我理解如何使用API​​,但也想明白为什么要这样devise。

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

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可以跟踪和更新CancellationToken,并且对于每个令牌,取消检查是本地字段访问?

在这两种情况下,如果一个不locking的volatile BOOL就足够了,我还是不明白为什么会更快。

谢谢!

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

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

如果因为行为不端API而敞开大门,则取消devise的实用性很快就会被削弱。

他们不是技术上的原因,而是语义上的原因。 如果你看看ILSpy下的CancellationToken的实现,你会发现它仅仅是一个关于CancellationTokenSource的包装(因此没有任何不同于传递参考的性能)。

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

我有完全相同的问题,我想了解这个devise背后的基本原理。

接受的答案得到了完全正确的理由。 以下是devise此function的团队的确认(重点是我的):

两种新types构成框架的基础:CancellationToken是一个表示“取消潜在请求”的结构。 这个结构作为parameter passing给方法调用,方法可以轮询它,或者在请求取消时注册一个callback。 CancellationTokenSource是一个类,它提供了启动一个取消请求的机制,它有一个Token属性来获得关联的标记。 将这两个类合并成一个类似是很自然的, 但是这种devise允许两个关键操作(发起取消请求,而不是观察和响应取消)完全分离。 特别是,只取消CancellationToken的方法可以观察取消请求,但不能启动取消请求。

链接: .NET 4取消框架

在我看来,CancellationToken只能观察状态而不改变它的事实是非常关键的。 您可以像糖果一样分发令牌,不要担心除您之外的其他人会取消它。 它保护您免受敌对第三方代码。 是的,机会渺茫,但我个人喜欢这个保证。

我也觉得它使API更清洁,避免了意外的错误,并促进了更好的组件devise。

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

CancellationToken API

CancellationTokenSource API

如果你把它们合并起来,在写LongRunningFunction的时候,我会看到类似'Cancel'的多重重载的方法,我不应该使用它们。 就我个人而言,我讨厌看到Dispose方法。

我认为目前的课堂devise遵循“成功之路”的理念,它指导开发人员创build更好的组件,可以处理任务取消,然后以多种方式将其组合在一起,以创build复杂的工作stream程。

让我问你一个问题,你有没有想知道token.Register的目的是什么? 这对我没有意义。 然后我阅读“ pipe理线程中的取消” ,一切都变得清晰起来。

我相信TPL的取消框架devise绝对是一stream的。

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

CancellationTokenSource在源上调用Cancel时设置令牌的所有副本的状态。 看到这个MSDN页面

devise的原因可能只是关注点和结构速度的问题。

CancellationTokenSource是出于任何原因发出取消的“事物”。 它需要一种方法将“取消”发送给它已经发出的所有CancellationToken。 例如,ASP.NET可以在请求被中止时取消操作。 每个请求都有一个CTSource,将取消消息转发给它发出的所有令牌。

这非常适合unit testingBTW – 创build您自己的取消令牌源,获取令牌,在源头上调用Cancel,并将令牌传递给必须处理取消的代码。

我的“理论”是CancellationToken的作用是提供一个线程安全的包装来避免争用

如果只有一个类,那么一个唯一的实例将被引用副本使用,并且你有很multithreading注册处理程序来取消,例如,这个类应该使用一些locking来pipe理并发,从而降低性能。

而在CancellationToken结构中 ,每个线程都有一个每个callback队列的副本,所以没有争用。

这是纯粹的猜测,我可能是完全错误的,和/或可能有这个devise的其他很好的理由。

唯一可以肯定的是,至less有一个很好的理由,这是一个耻辱,这种决定没有更多的文件,因为它是一个非常宝贵的洞察力,当使用技术。