使用CancellationTokens替代事件的优缺点是什么?

问题描述

最近,我遇到了一个非常不寻常的API,a Microsoft interface

public interface IHostApplicationLifetime
{
    public CancellationToken ApplicationStarted { get; }
    public CancellationToken ApplicationStopping { get; }
    public CancellationToken ApplicationStopped { get; }
}

属性ApplicationStopping的文档令人困惑地暗示,该属性实际上是事件添加了重点):

在应用程序主机执行正常关闭时触发。关机将阻止,直到此事件完成。

似乎应该将传统的EventHandler事件替换为CancellationToken属性。这就是我所期望的 界面为:

public interface IHostApplicationLifetime
{
    public event EventHandler ApplicationStarted;
    public event EventHandler ApplicationStopping;
    public event EventHandler ApplicationStopped;
}

我的问题是,这两个通知机制是否等效?如果不是,从API设计人员的角度来看,每种方法的优缺点是什么?在什么情况下CancellationToken属性优于经典事件?

解决方法

Microsoft应该更仔细地阅读文档。将CancellationTokens描述为要“触发”的事件令人困惑。

我当然不是C#语言专家,所以希望社区能够让我们知道我是否缺少导入点。但是让我刺几件事...

所以您的问题:它们是否等效 ..仅在它们都提供注册和调用回调的方式的意义上。但是由于许多其他原因,不。

CancellationToken围绕CancellationTokenSource进行包装。它们与Task实现紧密结合。它们是线程安全的。它们可以从外部调用,也可以由计时器调用。您可以将多个CancellationTokenSource链接在一起。它们保持状态,指示它们是否被取消,您可以查询该状态。

语言功能

C#events在文档中指的是特殊的多播委托。它们只能在声明它们的类中调用。 They are not really thread safe。它们被烘焙到XAML和WPF中。

我不会在一个比另一个优越的地方发表过多评论,因为它们有很大的不同。在正常情况下,我不认为您会考虑使用CancellationTokens的情况下的事件。在IHostApplicationLifetime的情况下,它们有重叠之处,主要是因为文档不正确以及.NET Core托管基础结构的重新设计。正如@EricLippert提到的,link对此提供了很好的概述。

,

CancellationToken与经典事件不同。差异很多,最明显的是CancellationToken只能被触发(取消)一次。因此,如果一次事件被多次提出是有意义的,则没有两难选择:经典的event是唯一的选择。因此,必须将比较范围缩小到一次性事件的情况,其中CancellationToken具有很多优点,而只有一个潜在的缺点。首先,优势

  1. CancellationToken不仅通知其订户将来可能发生的取消,而且还通知已经发生的取消。在具有经典事件和bool字段的多线程应用程序中尝试执行相同操作会创建竞争条件。在检查bool字段与订阅该事件之间可能会触发该事件,在这种情况下,通知将丢失。

  2. 即使事件处理程序是匿名lambda函数,也可以将事件处理程序附加到CancellationToken上和将其分离。对于经典事件,这是不可能的。 detach operator-=)需要与订阅(+=)先前传递的处理程序相同,因此必须将处理程序分配给变量或命名函数。>

  3. 可以将CancellationToken作为参数传递给另一个类的方法,以允许注册(也可以取消注册)回调。对于经典事件,这是不可能的。 C#语言does not allow it。实现此功能的唯一方法是将附加/分离lambda作为参数传递给方法otherClass.SomeMethod(h => this.MyEvent += h,h => this.MyEvent -= h)。这比otherClass.SomeMethod(this.MyToken)复杂,笨拙且可读性较差。

  4. 有成千上万个接受CancellationToken参数的API。这使得在多种情况下使用此类通知非常方便。相反,可以通过接受addHandler / removeHandler参数消耗事件的API极为罕见(例如:Observable.FromEventPattern)。

  5. 使用方法Unregister¹从CancellationToken注销回调,无论是否已调用该回调,都会提供bool反馈。相反,使用-=运算符取消订阅经典事件不会得到任何反馈。这意味着在多线程应用程序中,调用者无法知道分离的处理程序是否已在另一个线程上运行,因此它可以安全地处置该处理程序引用的所有可处理资源。这就给调用者留下了尴尬的选择,例如不处理资源或在处理程序内部捕获可能的ObjectDisposedException

使用CancellationToken作为一次性通知的缺点纯粹是语义上的。这种类型与取消的概念紧密相关,因此使用它来通知例如开始或停止的某件事很有可能引起混乱。

¹不适用于.NET Framework,因此此优势不适用于此平台。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...