172

I'm looking for a rationale of why .NET CancellationToken struct was introduced in addition to CancellationTokenSource class. I understand how the API is to be used, but want to also understand why it is designed that way.

I.e., why do we have:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts.Token);

...
public void SomeCancellableOperation(CancellationToken token) {
    ...
    token.ThrowIfCancellationRequested();
    ...
}

instead of directly passing CancellationTokenSource around like:

var cts = new CancellationTokenSource();
SomeCancellableOperation(cts);

...
public void SomeCancellableOperation(CancellationTokenSource cts) {
    ...
    cts.ThrowIfCancellationRequested();
    ...
}

Is this a performance optimization based on the fact that cancellation state checks happen more frequently than passing the token around?

So that CancellationTokenSource can keep track of and update CancellationTokens, and for each token the cancellation check is a local field access?

Given that a volatile bool with no locking is enough in both cases, I still can't see why that would be faster.

Thanks!

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Andrey Tarantsov
  • 8,965
  • 7
  • 54
  • 58

5 Answers5

166

I was involved in the design and implementation of these classes.

The short answer is "separation of concerns". It is quite true that there are various implementation strategies and that some are simpler at least regarding the type system and initial learning. However, CTS and CT are intended for use in a great many scenarios (such as deep library stacks, parallel computation, async, etc) and thus was designed with many complex use cases in mind. It is a design intended to encourage successful patterns and discourage anti-patterns without sacrificing performance.

If the door was left open for misbehaving APIs, then the usefulness of the cancellation design could quickly be eroded.

CancellationTokenSource == "cancellation trigger", plus generates linked listeners

CancellationToken == "cancellation listener"

Pang
  • 9,564
  • 146
  • 81
  • 122
Mike Liddell
  • 1,971
  • 1
  • 12
  • 9
  • http://stackoverflow.com/questions/39077497/how-does-the-timeout-apply-on-streamsocket-readasync# Do you have idea what should be default timeout for inputstream of the StreamSocket? When I use cancellationtoken to cancel read operation, it also close the concerned socket. Can there be any way to overcome this issue? – sam18 Aug 24 '16 at 06:10
  • 1
    @Mike -- Just curious: how come you can't call something like ThrowIfCancellationRequested() on a CTS like you can on a CT? – rory.ap Jun 16 '17 at 17:15
  • They have different responsibilities and its not too verbose to write cts.Token.ThrowIfCancellationRequested() so we didn't add the listener API directly on CTS. – Mike Liddell Jun 19 '17 at 17:47
  • @Mike Why cancellationtoken is a struct and not class? I don't see any performarmance advantages – Neir0 Oct 19 '18 at 20:36
  • structs are a value type and do not have the per-instance overhead of objects (header bytes, garbage collection). – Mike Liddell Jun 20 '19 at 19:51
  • Would be nice if this answer actually showed some examples of misbehaving APIs. – boatcoder Aug 11 '21 at 04:03
  • 3
    `CancellationTrigger` and `CancellationListener` are better names. I wonder if there's a path to change something like that. – Vimes May 18 '22 at 16:22
111

I had the exact question and I wanted to understand the rationale behind this design.

The accepted answer got the rationale exactly right. Here's the confirmation from the team who designed this feature (emphasis mine):

Two new types form the basis of the framework: A CancellationToken is a struct that represents a ‘potential request for cancellation’. This struct is passed into method calls as a parameter and the method can poll on it or register a callback to be fired when cancellation is requested. A CancellationTokenSource is a class that provides the mechanism for initiating a cancellation request and it has a Token property for obtaining an associated token. It would have been natural to combine these two classes into one, but this design allows the two key operations (initiating a cancellation request vs. observing and responding to cancellation) to be cleanly separated. In particular, methods that take only a CancellationToken can observe a cancellation request but cannot initiate one.

Link: .NET 4 Cancellation Framework

In my opinion, the fact that CancellationToken can only observe the state and not change it, is extremely critical. You can hand out the token like a candy and never worry that someone else, other than you, will cancel it. It protects you from hostile third party code. Yes, the chances are slim, but I personally like that guarantee.

I also feel that it makes the API cleaner and avoids accidental mistake and promotes better component design.

Let's look at public API for both of these classes.

CancellationToken API

CancellationTokenSource API

If you were to combine them, when writing LongRunningFunction, I will see methods like those multiple overloads of 'Cancel' which I should not be using. Personally, I hate to see Dispose method as well.

I think the current class design follows 'pit of success' philosophy, it guides developers to create better components which can handle Task cancellation and then instrument them together in numerous way to create complicated workflows.

Let me ask you a question, have you wondered what is the purpose of token.Register? It didn't make sense to me. And then I read Cancellation in Managed Threads and everything became crystal clear.

I believe that the Cancellation Framework Design in TPL is absolutely top notch.

riQQ
  • 9,878
  • 7
  • 49
  • 66
SolutionYogi
  • 31,807
  • 12
  • 70
  • 78
  • 9
    If you wonder how the `CancellationTokenSource` can actually initiate the cancel request on it's associated token (the token cannot do it by itself): The CancellationToken has this internal constructor: `internal CancellationToken(CancellationTokenSource source) { this.m_source = source; }` and this property: `public bool IsCancellationRequested { get { return this.m_source != null && this.m_source.IsCancellationRequested; } }` The CancellationTokenSource uses the internal constructor, so the token has reference to the source (m_source) – chviLadislav Mar 28 '17 at 09:55
  • Why would you include "Hostile" 3rd party code in your application? – boatcoder Aug 11 '21 at 03:19
66

They are separate not for technical reasons but semantic ones. If you look at the implementation of CancellationToken under ILSpy, you'll find it's merely a wrapper around CancellationTokenSource (and thus no different performance-wise than passing around a reference).

They provide this separation of functionality to make things more predictable: when you pass a method a CancellationToken, you know you're still the only one that can cancel it. Sure, the method could still throw a TaskCancelledException, but the CancellationToken itself -- and any other methods referencing the same token -- would remain safe.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110
  • Thanks! My blink reaction is that, semantically, it's a questionable approach, on par with providing IReadOnlyList. It does sound very plausible, though, so accepting your answer. – Andrey Tarantsov Jan 08 '13 at 14:15
  • 2
    Upon further thinking, I find myself questioning the plausibility. Wouldn't it make more sense, then, to provide ICancellationToken interface that CancellationTokenSource would implement? I wish someone from .NET team would chime in. – Andrey Tarantsov Jan 08 '13 at 14:20
  • 1
    That might still result in a crafty programmer casting to `CancellationTokenSource`. You'd think you could just say "don't do that", but people (including me!) do occasionally do these things anyway to get at some hidden functionality, and it'd happen. That's my current theory, at least. – Cory Nelson Jan 08 '13 at 14:45
  • 1
    That's not how you design APIs in most cases, unless it's security-related. I would rather bet on some other explanation, like “keeping their options open for performance optimizations in the future”. But still, yours is the best one so far. – Andrey Tarantsov Jan 09 '13 at 12:32
  • Hey, I hope you'll excuse me re-assigning the accepted answer to the person involved in the implementation. While you both say the same thing, I think SO benefits from the definitive answer being on top. – Andrey Tarantsov Mar 23 '15 at 06:12
  • @CoryNelson: Excellent distinction drawn in your answer. But: if you follow the example code provided in the `Task.Run()` help, the thread (as a closure) has access to both the CTS and the CT; there is no protection there. And providing the token via the `Task.Run(Action, Token)` doesn't actually **pass** the token in to the thread—I mean, wouldn't it make more sense for the signature to be `Task.Run(Action, Token)` ? – Mike C Oct 01 '15 at 23:47
  • @MikeC Yep, passing a token into Task.Run is a pretty rare application since it will only cancel if the token triggers *before* the action starts... generally you always want to pass it into the action itself. – Cory Nelson Oct 02 '15 at 00:16
  • Each Token you get from the Token property has a different wait handle. It isn't a simplistic wrapper. – Kind Contributor Oct 18 '20 at 01:11
11

The CancellationToken is a struct so many copies could exist due to passing it along to methods.

The CancellationTokenSource sets the state of ALL copies of a token when calling Cancel on the source. See this MSDN page

The reason for the design might be just a matter of separation of concerns and the speed of a struct.

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Emond
  • 50,210
  • 11
  • 84
  • 115
  • 1
    Thanks. Note that another answer tells that technically (in IL), CancellationTokenSource does not set the state of tokens; instead, the tokens wrap an actual reference to CancellationTokenSource, and simply access it to check for cancellation. If anything, the speed can only be lost here. – Andrey Tarantsov Jan 08 '13 at 14:24
  • +1. I don't understand. if its a value type so each method has its own value (separate value). so how does a method knows if a cancel has been called ? it doesn NOT have any reference to the TokenSource. all the method can see is a local value type like "5". can you explain ? – Royi Namir Mar 22 '13 at 13:56
  • 1
    @RoyiNamir: Each CancellationToken has private reference "m_source" of type CancellationTokenSource – Andreyul Feb 07 '14 at 17:51
2

The CancellationTokenSource is the 'thing' that issues the cancellation, for whatever reason. It needs a way to 'dispatch' that cancellation to all the CancellationToken's it has issued. That's how, for example, ASP.NET can cancel operations when a request is aborted. Each request has a CancellationTokenSource that forwards the cancellation to all the tokens it has issued.

This great for unit testing BTW - create your own cancellation token source, get a token, call Cancel on the source, and pass the token to your code that has to handle the cancellation.

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
n8wrl
  • 19,439
  • 4
  • 63
  • 103
  • Thanks — but both responsibilities (which _are_ very related) could be given to the same class. Does not really explain why they are separate. – Andrey Tarantsov Jan 08 '13 at 14:22