Search code examples
c#multithreadingtask-parallel-librarycancellationtokensourcecancellation-token

Why CancellationToken is separate from CancellationTokenSource?


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!


Solution

  • 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"