Cancellation Tokens

Vaibhav • September 11, 2025

In the previous article, we explored how exceptions behave in asynchronous code and how to catch, log, and propagate them properly. But what if you want to stop an async operation before it completes? Maybe the user navigated away, or the data is no longer needed. That’s where cancellation tokens come in. They allow you to cancel long-running tasks cooperatively and cleanly.

In this article, we’ll learn how cancellation works in C#, how to use CancellationToken and CancellationTokenSource, and how to design async methods that respond to cancellation requests. We’ll also cover best practices, common mistakes, and how cancellation interacts with await, Task, and exception handling.

Why Cancellation Matters

Async operations often involve waiting-reading files, querying databases, calling APIs. If the operation becomes irrelevant (e.g., the user closes the app or switches tabs), continuing the task wastes resources and may degrade performance. Cancellation lets you stop the operation early and reclaim control.

Cancellation is especially important in UI applications, background services, and server-side code where responsiveness and scalability matter. It’s also a key part of designing robust and user-friendly APIs.

Cancellation in C# is cooperative. The task must check for cancellation and exit gracefully. The runtime does not forcibly kill the thread or task.

Introducing CancellationToken and CancellationTokenSource

Cancellation in .NET is managed using two types:

  • CancellationTokenSource - creates and controls the cancellation signal
  • CancellationToken - passed to tasks and methods to observe cancellation

Here’s a basic example:


CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task t = Task.Run(() =>
{
    while (!token.IsCancellationRequested)
    {
        Console.WriteLine("Working...");
        Thread.Sleep(500);
    }

    Console.WriteLine("Cancelled!");
}, token);

Thread.Sleep(2000);
cts.Cancel();
    

This code starts a task that checks the token periodically. After 2 seconds, we call Cancel() on the source, which signals the token. The task sees the signal and exits.

Using CancellationToken in Async Methods

To support cancellation in an async method, add a CancellationToken parameter and pass it to cancellable operations like Task.Delay, Stream.ReadAsync, or HttpClient.SendAsync.


async Task LoadDataAsync(CancellationToken token)
{
    await Task.Delay(1000, token);
    Console.WriteLine("Data loaded");
}
    

If the token is cancelled before or during the delay, Task.Delay throws a TaskCanceledException. You can catch this exception to handle cancellation gracefully.


try
{
    await LoadDataAsync(token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation was cancelled");
}
    

This pattern ensures that your method exits cleanly and doesn’t leave resources hanging.

ThrowIfCancellationRequested

Not all operations support cancellation tokens directly. In such cases, you can check the token manually and throw an exception to exit early.


async Task ProcessAsync(CancellationToken token)
{
    token.ThrowIfCancellationRequested();

    string data = await File.ReadAllTextAsync("data.txt");
    token.ThrowIfCancellationRequested();

    Console.WriteLine("Processing complete");
}
    

This method checks the token before and after reading the file. If cancellation is requested, it throws OperationCanceledException, which can be caught by the caller.

Always check the token before doing expensive work or after long-running operations. This keeps your method responsive to cancellation.

Linking Tokens

Sometimes you want to combine multiple cancellation sources-like a global timeout and a user-triggered cancel. You can use CancellationTokenSource.CreateLinkedTokenSource to merge them.


CancellationTokenSource userCts = new CancellationTokenSource();
CancellationTokenSource timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
    userCts.Token, timeoutCts.Token);

await DoWorkAsync(linkedCts.Token);
    

This token is cancelled if either the user cancels or the timeout expires. It’s a powerful way to compose cancellation logic.

Handling Cancellation Exceptions

When a cancellable operation is aborted, it throws OperationCanceledException. This is a subclass of Exception and should be handled separately from other errors.


try
{
    await Task.Delay(1000, token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Cancelled by user");
}
catch (Exception ex)
{
    Console.WriteLine("Other error: " + ex.Message);
}
    

This pattern ensures that cancellation is treated as a normal control flow, not an error. You can also check token.IsCancellationRequested before catching to confirm the source.

Cancellation in UI Applications

In UI apps, cancellation is often triggered by user actions-like clicking a cancel button or navigating away. You can store the CancellationTokenSource in a field and cancel it when needed.


CancellationTokenSource _cts;

async void StartButton_Click(object sender, EventArgs e)
{
    _cts = new CancellationTokenSource();

    try
    {
        await LoadDataAsync(_cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Cancelled by user");
    }
}

void CancelButton_Click(object sender, EventArgs e)
{
    _cts?.Cancel();
}
    

This pattern gives users control over long-running operations and improves responsiveness.

Cancellation in Server Applications

In ASP.NET Core, cancellation tokens are built into the request pipeline. You can access HttpContext.RequestAborted to detect when the client disconnects or cancels the request.


public async Task GetData(CancellationToken cancellationToken)
{
    string data = await _service.LoadAsync(cancellationToken);
    return Ok(data);
}
    

This ensures that your server doesn’t waste resources on abandoned requests. It also improves scalability under load.

Common Mistakes

Cancellation is powerful but easy to misuse. Here are some common mistakes:

  • Not passing the token to cancellable operations
  • Swallowing OperationCanceledException without logging
  • Using async void and losing cancellation control
  • Not disposing CancellationTokenSource (use using or Dispose())

You can cancel a task before it starts by calling Cancel() on the source. The task will never run, and its status will be Canceled.

Summary

Cancellation tokens are a key part of writing responsive and scalable async code. They allow you to abort operations cleanly, reclaim resources, and improve user experience. You’ve learned how to use CancellationToken and CancellationTokenSource, how to pass tokens to async methods, how to handle cancellation exceptions, and how to design cancellable APIs for UI and server applications.

In the next article, we’ll explore Task Parallelism, where we’ll learn how to run multiple tasks concurrently, coordinate their results, and build high-performance parallel workflows using the Task class.