简体   繁体   中英

What are the pros and cons of using CancellationTokens as alternatives to events?

Recently I came across a Microsoft interface with a quite unusual API:

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

The documentation of the property ApplicationStopping suggests confusingly that this property is actually an event (emphasis added):

Triggered when the application host is performing a graceful shutdown. Shutdown will block until this event completes.

It seems that what should be a traditionalEventHandler event, has been replaced with a CancellationToken property. This is how I expected this interface to be:

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

My question is, are these two notifications mechanisms equivalent? If not, what are the pros and cons of each approach, from the perspective of an API designer? In which circumstances a CancellationToken property is superior to a classic event?

Microsoft should have been more careful with the documentation. It is confusing to describe the CancellationTokens as events to be "triggered".

I'm certainly not a C# language expert so hopefully the community will let us know if I'm missing import points. But let me take a stab a few things...

So your question: are they equivalent .. only in the sense that they both provide a way to register and invoke callbacks. But for a lot of other reasons, no.

CancellationToken s are wrapper around CancellationTokenSource . They tie into the Task implementation. They are thread safe. They can be invoked externally and can be invoked by a timer. You can chain multiple CancellationTokenSource together. They maintain state which indicates if they are, or are not, canceled and you can query that state.

C# events , the language feature, are in the words of the documentation a special multicast delegate. They can only be invoked within the class where they are declared. They are not really thread safe . They are baked into XAML and WPF.

I'm not going to comment too much on where one is superior to the other as they are very different. In the normal course I don't think you'd consider events in situations where you would consider CancellationTokens. They overlapped in the case of IHostApplicationLifetime mainly because of bad documentation and the redesign of the hosting infrastructure for .NET Core. As @EricLippert mentioned this link provides a great overview of that.

The CancellationToken is not equivalent with the classic events. The differences are numerous, with the most obvious being that the CancellationToken can be triggered (canceled) only once. So in case it makes sense for an event to be raised more than once, there is no dilemma: a classic event is the only option. So the comparison must be narrowed to the cases of one-time-events, where the CancellationToken has many advantages, and only one potential disadvantage. First the advantages :

  1. The CancellationToken notifies its subscribers not only about a cancellation that may happen in the future, but also about a cancellation that has already happened. Trying to do the same in a multithreaded application with a classic event and a bool field creates a race condition. It is possible for the event to be triggered between checking the bool field and subscribing to the event, in which case the notification will be lost.

  2. It is possible to attach and detach an event handler to a CancellationToken , even if the handler is an anonymous lambda function. This is not possible with a classic event. The detach operator ( -= ) requires the same handler that was passed previously at the subscription ( += ), so the handler must be assigned to a variable, or be a named function.

  3. It is possible to pass a CancellationToken as an argument to a method of another class, to allow registering (and optionally unregistering) a callback. This is not possible with the classic events. The C# language does not allow it . The only way to achieve this functionality is by passing attach/detach lambdas as arguments to the method: otherClass.SomeMethod(h => this.MyEvent += h, h => this.MyEvent -= h) . This is complicated, awkward, and less readable than otherClass.SomeMethod(this.MyToken) .

  4. There are thousands of APIs that accept a CancellationToken parameter. This makes the consumption of this kind of notification very convenient in a multitude of scenarios. On the contrary the APIs that can consume events by accepting addHandler / removeHandler arguments are extremely rare (example: Observable.FromEventPattern ).

  5. Unregistering a callback from a CancellationToken with the method Unregister ¹ gives a bool feedback whether the callback has already been invoked or not. On the contrary unsubscribing from a classic event with the -= operator gives no feedback. This means that in a multithreaded application the caller has no way of knowing whether the detached handler is already running on another thread, so that it can safely dispose any disposable resources referenced by the handler. This leaves the caller with awkward options like not disposing the resources, or catching possible ObjectDisposedException s inside the handler.

The disadvantage of using a CancellationToken as an one-time-notification is purely semantic. This type is strongly associated with the concept of cancellation, so using it to notify that, for example, something started or stopped, has great chances to create confusion.

¹ Not available for the .NET Framework, so this advantage does not apply for this platform.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM