Cancel WhenAny - linked CancellationTokenSource

28/05/2024

In today's short post, I will show you how to cancel a Task with multiple inputs (aka multiple CancellationTokens) using CancellationTokenSource and CancellationTokenSource.CreateLinkedTokenSource.

Problem

Imagine you have a long-running task you want to shutdown gracefully (or not) and have to listen to multiple sources of cancellation. That can be common in BackgroundService or IHostedService implementations where you want to listen to the IHostApplicationLifetime.ApplicationStopping event and also to some other event or condition.

// Triggered when the application host is starting a graceful shutdown.
var hostToken = IHostApplicationLifetime.ApplicationStopping; 

// Some other source for cancellation passed in by the method
var otherToken = ...;

Now you want to create a CancellationToken that will be triggered when either of the tokens is cancelled. But a method normally only accepts a single token:

await Task.Delay(Timeout.Infinite, token); // You can't pass two tokens here

The solution: CancellationTokenSource.CreateLinkedTokenSource. It creates a new CancellationTokenSource that will be canceled when any of the linked tokens is canceled. You can pass in as many tokens as you want and it returns a new CancellationTokenSource, which you can use to create a CancellationToken for your task.

using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(hostToken, otherToken);
var linkedToken = linkedTokenSource.Token;

await Task.Delay(Timeout.Infinite, linkedToken); // Now you can pass the linked token

Be aware that CancellationTokenSource is an IDisposable and should be disposed of when you are done with it. The using statement is a good way to ensure that.

You can also cancel the linked token source manually to indicate that the task should be canceled. The "source" tokens will stay unaffected, only the linked token will be canceled.

using var t1 = new CancellationTokenSource();
using var t2 = new CancellationTokenSource();
using var t3 = CancellationTokenSource.CreateLinkedTokenSource(t1.Token, t2.Token);

t3.Cancel();

Console.WriteLine(t1.IsCancellationRequested); // False
Console.WriteLine(t2.IsCancellationRequested); // False
Console.WriteLine(t3.IsCancellationRequested); // True
7
An error has occurred. This application may no longer respond until reloaded. Reload x