How to cancel a CancellationToken

73.3k views Asked by At

I start a task, that starts other tasks and so forth. Given that tree, if any task fails the result of the whole operation is useless. I'm considering using cancellation tokens. To my surprise, the token does not have a "CancelThisToken()" method...

How can I, in possession of only a CancellationToken, cancel it?

5

There are 5 answers

15
RyanS On BEST ANSWER

As the documentation states, you need to call the Cancel() method from the token source, not the token itself. Note the example code in the CancellationToken Struct documentation:

// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
...
source.Cancel();

how can I, in possession of only a CancellationToken, cancel it?

Without a reference to the source you cannot cancel the token, this is by design.

As a flawed workaround, when given a CancellationToken, you can create a new instance of the token source, assign its token to the provided token, and cancel the new source:

// Define the cancellation token.
CancellationTokenSource newSource = new CancellationTokenSource();
existingToken = newSource.Token;
...
newSource.Cancel();
// "existingToken" is cancelled hereafter

...but this will only affect downstream consumers of the token. Any entities with the token prior to updating the reference will still have the original, uncancelled token.

But do note that if you're creating the token to track tasks, then you do have the source, so this shouldn't be an issue.

1
Machinarius On

Spawn CancellationToken instances from a CancellationTokenSource instance and call Cancel on the CTS instance.

Example: Cancel()

There's also a way to gracefully cancel threads without them firing exceptions. Just check the CT for IsCancellationRequested and handle the case yourself.

More information: Use of IsCancellationRequested property?

0
Daniel Park On

As an extension of the answers provided so far, if you want to have both a CancellationToken instance provided to your methods, and cancel internally, you should examine CancellationTokenSource.CreateLinkedTokenSource. In essence this will cancel either when cts.Cancel() is called, or one of its supplied tokens is.

3
Tore Aurstad On

Several answers here has stated that it is considered a hack and anti-pattern to on-demand cancel a cancellation token without using the original CancellationTokenSource. If you still want to do this and not throw an exception like using a combination of IsCancellationRequested and ThrowIfCancellationRequested method, you can create an extension method to on-demand cancel a cancellation token.

public static class CancellationTokenExtensions
{    
    public static void ForceCancel(ref this CancellationToken cancellationToken, 
    Func<bool>? condition = null)
    {
        if (condition == null || condition.Invoke())
        {
            var cts = CancellationTokenSource.CreateLinkedTokenSource(
            cancellationToken);
            cancellationToken = cts.Token;
            cts.Cancel();
        }
    }
}

We use ref here since CancellationToken is a struct and want to modify the token object and must take care here since we pass a struct by value otherwise into the extension method. This is supported since C# 7.3.

If you use older C#, return the CancellationToken object and overwrite the cancellation token you passed in (probably not use this argument then since you overwrite it anyways).

Sample source code in a demo app in a Pluralsight course I am looking at right now shows how I added usage of this method inside a MVC controller action. Ignore much of the surrounding code here, point is that we can now easily cancel on demand a cancellation token. I have tested the code in Swagger API that display the controller and tested that it works.

    [HttpGet]
    public async Task<IEnumerable<ProductModel>> Get(CancellationToken cancelToken, 
    string category = "all")
    {
        //cancelToken.ForceCancel();
        cancelToken.ForceCancel(() => category == "kayak");

        using (_logger.BeginScope("ScopeCat: {ScopeCat}", category))
        {     
            _logger.LogInformation( "Getting products in API.");
            return await _productLogic.GetProductsForCategoryAsync(cancelToken, 
            category);
        }
    }

We can skip the condition argument if we want to, then the cancellation token will be canceled, as soon as possible and throw an OperationCancelled exception in your downstream code. Remember to pass the updated token downwards, if you use C# 7.3 or newer, the object is updated automatically for you using this code. You will see that IsCancellationRequested is true, when you inspect the updated token (which now is a new struct).

Should you also keep track of the newly instantiated CancellationTokenSource inside the cts variable in the method ? This could also be an option, letting the extension method return CancellationTokenSource you created here and save this to a var in the calling code. That object is a class by the way.

1
andrew pate On

A token gives you the right to know someone is trying to cancel something. It does not give you the right to actually signal a cancellation. Only the cancellation token source gives you that. This is by design.