Skip to main content

.NET Cancellation Tokens

Introduction

When working with asynchronous operations in .NET, it's often necessary to cancel operations that are taking too long or are no longer needed. This is where cancellation tokens come into play. Cancellation tokens provide a standardized way to communicate cancellation requests across multiple components and threads in your application.

In this tutorial, you'll learn:

  • What cancellation tokens are and why they're important
  • How to create and use cancellation tokens
  • Best practices for implementing cancellation in your code

Understanding Cancellation Tokens

What is a Cancellation Token?

A cancellation token is an object that represents a request to cancel one or more operations. In .NET, cancellation tokens are implemented through two primary classes:

  1. CancellationToken: A struct that represents a cancellation notification
  2. CancellationTokenSource: A class that creates and manages cancellation tokens

The relationship is straightforward: you create a CancellationTokenSource, get a CancellationToken from it, and pass that token to operations that support cancellation.

Creating and Using Cancellation Tokens

Let's start with a basic example:

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

public class Program
{
public static async Task Main()
{
// Create a cancellation token source
CancellationTokenSource cts = new CancellationTokenSource();

// Get a token from the source
CancellationToken token = cts.Token;

// Start a task that accepts the cancellation token
Task longRunningTask = LongRunningOperationAsync(token);

// Simulate waiting for 2 seconds and then canceling
await Task.Delay(2000);
Console.WriteLine("Canceling the operation...");
cts.Cancel();

try
{
await longRunningTask;
Console.WriteLine("The task completed successfully.");
}
catch (OperationCanceledException)
{
Console.WriteLine("The task was canceled.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}

Console.WriteLine("Main method complete.");
}

static async Task LongRunningOperationAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Long running operation started...");

// Simulate work
for (int i = 0; i < 10; i++)
{
// Check if cancellation was requested
cancellationToken.ThrowIfCancellationRequested();

Console.WriteLine($"Working... {i + 1}/10");
await Task.Delay(1000, cancellationToken);
}

Console.WriteLine("Long running operation completed.");
}
}

Output:

Long running operation started...
Working... 1/10
Working... 2/10
Canceling the operation...
The task was canceled.
Main method complete.

Breaking Down the Example

  1. We create a CancellationTokenSource using new CancellationTokenSource().
  2. We get a token from that source using the Token property.
  3. We pass this token to our LongRunningOperationAsync method.
  4. After 2 seconds, we call cts.Cancel() to request cancellation.
  5. In our long-running operation, we periodically check for cancellation by calling cancellationToken.ThrowIfCancellationRequested().
  6. We also pass the token to Task.Delay() which supports cancellation.

Handling Cancellation in Different Scenarios

Cancellation with Timeout

You can set a cancellation token to automatically cancel after a specified timeout:

csharp
// Cancel after 5 seconds
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

// Or
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000); // 5000 milliseconds

Combining Multiple Cancellation Tokens

Sometimes you need to cancel an operation if any one of several conditions occurs:

csharp
CancellationTokenSource cts1 = new CancellationTokenSource();
CancellationTokenSource cts2 = new CancellationTokenSource();

// Create a linked token source that will be canceled when either source is canceled
using (CancellationTokenSource linkedCts =
CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token))
{
// Use linkedCts.Token for operations that should be canceled if either source is canceled
Task longRunningTask = LongRunningOperationAsync(linkedCts.Token);

// Later, cancel one of the sources
cts1.Cancel();
// The linkedCts will also be canceled
}

Manually Checking Cancellation Status

Sometimes, instead of throwing an exception, you might want to check if cancellation has been requested:

csharp
static async Task ProcessDataAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 1000; i++)
{
// Check cancellation without throwing
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Cancellation requested, cleaning up...");
// Perform cleanup operations
return;
}

// Process data
await ProcessItemAsync(i);
}
}

Real-World Example: Implementing a Cancellable Web API Request

Here's a more practical example showing how to implement cancellation in a Web API client:

csharp
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public class WeatherService
{
private readonly HttpClient _httpClient;

public WeatherService()
{
_httpClient = new HttpClient();
}

public async Task<string> GetWeatherForecastAsync(string city, CancellationToken cancellationToken = default)
{
try
{
string url = $"https://api.example.com/weather?city={city}";

// The HttpClient methods accept a cancellation token
HttpResponseMessage response = await _httpClient.GetAsync(url, cancellationToken);

response.EnsureSuccessStatusCode();

// ReadAsStringAsync also accepts a cancellation token
return await response.Content.ReadAsStringAsync(cancellationToken);
}
catch (OperationCanceledException)
{
Console.WriteLine("Weather request was canceled.");
throw; // Re-throw to let the caller handle it
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Weather request failed: {ex.Message}");
throw;
}
}
}

// Usage example:
public async Task GetWeatherWithTimeoutAsync()
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
try
{
var weatherService = new WeatherService();
string forecast = await weatherService.GetWeatherForecastAsync("Seattle", cts.Token);
Console.WriteLine($"Weather forecast: {forecast}");
}
catch (OperationCanceledException)
{
Console.WriteLine("The weather request timed out after 5 seconds.");
}
}
}

Best Practices for Using Cancellation Tokens

  1. Accept cancellation tokens as parameters: Design your asynchronous methods to accept a CancellationToken parameter, preferably as the last parameter with a default value.
csharp
public Task DoWorkAsync(string input, CancellationToken cancellationToken = default)
{
// Implementation
}
  1. Propagate cancellation tokens: Pass the token to other async methods you call.
csharp
public async Task ParentMethodAsync(CancellationToken cancellationToken)
{
// Pass the token to child methods
await ChildMethod1Async(cancellationToken);
await ChildMethod2Async(cancellationToken);
}
  1. Check for cancellation at appropriate points: Check for cancellation at logical points in your code.

  2. Clean up resources: When cancellation is requested, make sure to clean up any resources you've allocated.

  3. Don't ignore cancellation exceptions: Handle OperationCanceledException appropriately.

  4. Dispose of cancellation token sources: Use the using statement or call Dispose() when you're done with a CancellationTokenSource.

Common Pitfalls

  1. Not disposing CancellationTokenSource: This can lead to memory leaks.
  2. Not handling OperationCanceledException: This can cause unhandled exceptions in your application.
  3. Checking for cancellation too infrequently: This can make your application unresponsive to cancellation requests.
  4. Checking for cancellation too frequently: This can impact performance.

Summary

Cancellation tokens provide a standardized mechanism for canceling asynchronous operations in .NET. They enable you to build more responsive applications that can handle cancellations gracefully.

Key points to remember:

  • Use CancellationTokenSource to create and signal cancellation
  • Pass CancellationToken to methods that support cancellation
  • Check for cancellation at appropriate points in your code
  • Handle OperationCanceledException where appropriate
  • Clean up resources when cancellation occurs
  • Always dispose of CancellationTokenSource instances

Exercises

  1. Create a console application that downloads multiple files concurrently and allows the user to cancel the downloads by pressing a key.

  2. Implement a search function that queries multiple APIs and can be canceled if it takes too long.

  3. Modify an existing async method in your codebase to accept a cancellation token and properly handle cancellation.

Additional Resources

Happy coding!



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)