4

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 traditional EventHandler 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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Sanky
  • 193
  • 13
  • `use an event vs a cancellation token` very different concepts and meaning altogether. Could you please update your post with what you've tried, what isn't working and expected output? – Trevor Oct 07 '20 at 13:31
  • An event, I am assuming you mean `System.Threading.AutoResetEvent` or ilk, is very different from a cancellation token. – Tanveer Badar Oct 07 '20 at 13:39
  • A `CancelationToken` can be baked into a method and make it cancelable. How would you do the same thing with an event? Can you update your question with an example of an alternative of, say, the [`BlockingCollection.Take`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1.take) method, that uses events instead of a `CancelationToken`? – Theodor Zoulias Oct 07 '20 at 13:57
  • I have updated the question and tried to put in more context. I think it should help you to understand this better @Çöđěxěŕ. – Sanky Oct 07 '20 at 14:45
  • @TheodorZoulias I have mentioned one instance where the confusion crept in. Have a look, I will try to have a look to what you have pointed. – Sanky Oct 07 '20 at 14:48
  • I suggest to change the type used in the example from `IApplicationLifetime` to [`IHostApplicationLifetime`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostapplicationlifetime), because the former is marked as obsolete. As for the question, are you interested about the reasons that led the API designers of this specific API to prefer a `CancelationToken` property over a `EventHandler` event, or you are interested about the pros and cons of these two notification mechanisms in general? – Theodor Zoulias Oct 07 '20 at 15:15
  • @TheodorZoulias I know about the deprecation but both are same with respect to the question. Regarding what I am looking for, I am actually looking for both that you have mentioned, basically when to use what. – Sanky Oct 07 '20 at 16:36
  • Any idea how to re-open this question, it is marked closed now, don't know why. – Sanky Oct 07 '20 at 16:39
  • I think that your question is somewhat poorly presented. You could make it clearer that your are interested about the pros and cons of two alternative designs, from the perspective of the API designer. You could also include examples of the two alternative designs in your question, instead of forcing the readers to follow an external link in order to understand what you are talking about. Without these problems your question would be perfectly fine and quite interesting. Personally I have already voted for the question to be reopened. 2 more votes are required. – Theodor Zoulias Oct 07 '20 at 17:00
  • I could try to improve your question if you wanted to, but you might not be pleased with the outcome because I would have to change the title and the body of the question almost completely. – Theodor Zoulias Oct 07 '20 at 17:10
  • @TheodorZoulias Why vote to reopen a question if you think it needs to be re-written into an entirely different question in order to be an appropriate question? Additionally, asking for the pros/cons of a thing is *still* a very broad question, and also opinion based, so that's not even turning it into an appropriate question anyway. – Servy Oct 07 '20 at 17:58
  • @Servy I have offered to rewrite the question in order to make it clearer. The wording would be different, but the question would be the same. As for whether asking for pros and cons is too broad or opinion based, I personally think it's [n](https://stackoverflow.com/questions/944296/)[e](https://stackoverflow.com/questions/4181018/)[i](https://stackoverflow.com/questions/14260120/)[t](https://stackoverflow.com/questions/2098393/)[h](https://stackoverflow.com/questions/6600355/)[er](https://stackoverflow.com/questions/10708594/), hence my reopen vote. – Theodor Zoulias Oct 07 '20 at 18:17
  • @TheodorZoulias Again, why vote to reopen *before* fixing the problems you think need to be fixed in the question, rather than *after*? You've linked to a question asking for opinions and that doesn't have any verifiably correct answer, so in what way do you think it supports your assertion that that's an appropriate question? – Servy Oct 07 '20 at 18:22
  • @Servy fixing the problems would help persuade others that the question is worthy and deserves of being open. Personally I am already convinced. Even in its current poor form, the question can be understood (with some effort), and answered (if you know the answer). – Theodor Zoulias Oct 07 '20 at 18:36
  • @TheodorZoulias So you're voting to reopen a question that you feel is unclear, and of poor quality, because you're not interested in convincing anyone else that the question could theoretically become a good question if someone completely re-wrote it from scratch leaving nothing of the original left. That's...not how this site works. Questions are expected to be reopened *after* they're made clear, answerable, of good quality, etc. Then of course there's the fact that the question is specifically asking about people's personal preferences, which, by definition do not have a correct answer. – Servy Oct 07 '20 at 18:45
  • @Servy you are putting a lot of effort at questioning my motives and trying to convince me that I voted incorrectly. It's not worth it. If for no other reason, I cannot revoke my vote. It's already registered, and cannot be undone. And no harm has been done. The question remains as closed as it was before my vote. So I am done with this discussion and I am moving on. For humanity's sake, I hope you do the same. – Theodor Zoulias Oct 07 '20 at 19:01
  • @TheodorZoulias I am putting a small amount of effort in explaining to you how to use a site feature appropriately because you might use it again at some point in the future, and if you continue to use the feature improperly again, as you have here, you *will* be doing harm. That you're casting just one vote doesn't absolve you of responsibility to use the feature correctly. And while you can't retract your vote, if you feel the question can be improved, you *can* still improve it. – Servy Oct 07 '20 at 19:20
  • Sorry for getting back to this discussion after long, it was already late night in my timezone so was not really tracking. @TheodorZoulias thanks a lot for the help you offered. I would have loved to have your edits, as I was kind of wondering why this was not clear. Anyway I think the discussions have moved away quite a bit from what was it for originally, sadly. I think I will rather create a new question keeping in mind the suggestions. – Sanky Oct 08 '20 at 05:34
  • I'll try to improve your question. If you ask a second similar question, chances are that it will be closed as duplicate. People love to close questions here! – Theodor Zoulias Oct 08 '20 at 06:45
  • @TheodorZoulias that will be great! Also will help me to learn about posting questions, thank you. – Sanky Oct 08 '20 at 06:51
  • Sanky I just edited the question. If you are not satisfied with the edit you can edit it further, or you can click the [revisions](https://stackoverflow.com/posts/64245072/revisions) and click "rollback" to the previous version. – Theodor Zoulias Oct 08 '20 at 07:31
  • 2
    There is a very simple reason for the `CancellationToken` over `EventHandler`. With an EventHandler, you would not know if `ApplicationStarted` before your `EventHandler` registration ran. This means you would need to add a `bool IsApplicationStarted { get; }` property. The `CancellationToken` would also implicitely handle lifetime of the Event. – Aron Oct 08 '20 at 09:39
  • @TheodorZoulias thanks for your help, however the question is still 'closed'. Nevertheless there is some context already in the comments and that's all I need :) Cheers! – Sanky Oct 08 '20 at 10:54
  • @Aron that makes sense, thanks! – Sanky Oct 08 '20 at 10:54
  • Sanky there are currently 2 reopen votes. [One more is required](https://stackoverflow.com/help/reopen-questions) for the question to be reopened. – Theodor Zoulias Oct 08 '20 at 10:56
  • @Aron I can see the logic as you have described. Just an after thought- don't you think `ApplicationLifetime.isApplicationStarted` would have been more intuitive than `ApplicationLifetime.ApplicationStarted.IsCancellationRequested` ? – Sanky Oct 08 '20 at 11:30
  • Hi @TanveerBadar. The question has been edited, towards the goal of becoming more clear and specific. Could you review the question again, and check if it's now eligible for accepting answers? – Theodor Zoulias Oct 09 '20 at 17:43
  • 2
    There is a blog post explaining this architectural decision here: https://andrewlock.net/introducing-ihostlifetime-and-untangling-the-generic-host-startup-interactions/ Does that make it more clear? – Eric Lippert Oct 09 '20 at 18:20

2 Answers2

4

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.

CancellationTokens 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.

MikeJ
  • 1,299
  • 7
  • 10
  • +1 for the [link](https://blog.stephencleary.com/2009/06/threadsafe-events.html)! Regarding thread safety, after having read Stephen Cleary's article, it occurred to me that a `CancellationToken` may suffer from a similar race condition as the events: It is possible to dispose a [`CancellationTokenRegistration`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokenregistration) from Thread1 while the [registered](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken.register) callback is already running on Thread2. – Theodor Zoulias Oct 09 '20 at 21:50
  • But then I noticed the method [`Unregister`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokenregistration.unregister) that returns a `bool`. If the result is `true`, then you can safely dispose any disposable resources that are referenced by the callback, because it is guaranteed that the callback is neither running, nor will run in the future. I think that this feedback is the key that eliminates the race conditions that plague the events! – Theodor Zoulias Oct 09 '20 at 21:50
3

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 ObjectDisposedExceptions 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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104