How to Use CancellationTokenSource in C

Cooperative cancellation is a fundamental pattern in .NET for stopping long-running or asynchronous operations gracefully. This article explains how to implement cancellation tokens using CancellationTokenSource in C#, covering token creation, propagating tokens to asynchronous methods, checking for cancellation requests, and handling the resulting exceptions.

Understanding the Components

In .NET, cancellation is cooperative, meaning the executing operation must actively monitor and respect a cancellation request. This mechanism relies on two main types:

  • CancellationTokenSource (CTS): The object responsible for initiating and signaling a cancellation request via its Cancel() or CancelAfter() methods.
  • CancellationToken (CT): A lightweight struct generated by the CancellationTokenSource (via its .Token property) that is passed into methods to listen for cancellation signals.

Step-by-Step Implementation

Implementing cancellation requires instantiating the source, passing the token, polling or checking the token inside the work method, and signaling the cancellation.

1. Basic Asynchronous Example

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // 1. Create the CancellationTokenSource
        using var cts = new CancellationTokenSource();

        // 2. Start a background task, passing the Token
        Task task = DoWorkAsync(cts.Token);

        // Simulate some work in the caller, then cancel
        await Task.Delay(2000);
        cts.Cancel();

        try
        {
            await task;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("The operation was successfully canceled.");
        }
    }

    static async Task DoWorkAsync(CancellationToken cancellationToken)
    {
        for (int i = 0; i < 10; i++)
        {
            // 3. Inspect the token and throw if canceled
            cancellationToken.ThrowIfCancellationRequested();

            Console.WriteLine($"Working... Step {i + 1}");

            // Pass the token to built-in async methods
            await Task.Delay(1000, cancellationToken);
        }
    }
}

Key Cancellation Techniques

Throwing vs. Checking Manually

There are two primary ways to respond to a token's state inside a method:

  1. cancellationToken.ThrowIfCancellationRequested(): Throws an OperationCanceledException immediately if cancellation has been requested. This is the standard approach when an aborted operation should result in an exception.
  2. cancellationToken.IsCancellationRequested: A boolean property that allows for custom cleanup before exiting without necessarily throwing an exception:
if (cancellationToken.IsCancellationRequested)
{
    // Perform custom cleanup here
    return;
}

Implementing Timeouts with CancelAfter

CancellationTokenSource can automatically cancel an operation after a specific duration, which is useful for setting request timeouts:

// Cancel automatically after 5 seconds
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

// Alternatively, configure an existing source:
// cts.CancelAfter(TimeSpan.FromSeconds(5));

Combining Multiple Tokens

When an operation needs to listen to multiple cancellation sources—such as an external user request combined with an internal timeout—use CreateLinkedTokenSource:

using var internalTimeoutCts = new CancellationTokenSource(5000);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
    externalToken, 
    internalTimeoutCts.Token
);

// linkedCts triggers if either externalToken or internalTimeoutCts cancels
await PerformOperationAsync(linkedCts.Token);

Best Practices

  • Always dispose CancellationTokenSource: Instances of CancellationTokenSource often use internal timers (especially when using timeouts), so wrap them in a using statement or dispose of them explicitly.
  • Propagate tokens down the call stack: Forward the CancellationToken parameter to every downstream asynchronous method (e.g., HttpClient.SendAsync, Task.Delay, or database calls) to ensure that the entire call chain terminates promptly.
  • Catch OperationCanceledException: Always anticipate and handle OperationCanceledException (or TaskCanceledException) at the boundary where the operation is triggered.