15 minlesson

CancellationToken and CancellationTokenSource

CancellationToken and CancellationTokenSource

Cancellation is cooperative in .NET - operations must explicitly check for and respond to cancellation requests. The CancellationToken and CancellationTokenSource classes provide this mechanism.

The Basics

csharp
1// CancellationTokenSource - Controls cancellation
2var cts = new CancellationTokenSource();
3
4// CancellationToken - Passed to operations
5CancellationToken token = cts.Token;
6
7// Request cancellation
8cts.Cancel();
9
10// Check if cancellation requested
11bool isCanceled = token.IsCancellationRequested;

Why Cooperative Cancellation?

Forcefully terminating threads is dangerous:

  • Resources may not be cleaned up
  • Data may be left in inconsistent state
  • Locks may be held indefinitely

Cooperative cancellation lets operations:

  • Complete current work unit
  • Clean up resources
  • Save state if needed
  • Exit gracefully

Basic Pattern

csharp
1async Task ProcessDataAsync(CancellationToken cancellationToken)
2{
3 for (int i = 0; i < 1000; i++)
4 {
5 // Check for cancellation periodically
6 cancellationToken.ThrowIfCancellationRequested();
7
8 await ProcessItemAsync(i);
9 }
10}
11
12// Usage
13var cts = new CancellationTokenSource();
14
15try
16{
17 await ProcessDataAsync(cts.Token);
18}
19catch (OperationCanceledException)
20{
21 Console.WriteLine("Operation was canceled");
22}

Creating CancellationTokenSource

csharp
1// Basic creation
2var cts = new CancellationTokenSource();
3
4// With timeout - auto-cancels after delay
5var ctsWithTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
6
7// Cancel after a delay
8var cts2 = new CancellationTokenSource();
9cts2.CancelAfter(TimeSpan.FromSeconds(10));

CancellationToken Methods

csharp
1CancellationToken token = cts.Token;
2
3// Check if cancellation requested (doesn't throw)
4if (token.IsCancellationRequested)
5{
6 // Clean up and exit
7 return;
8}
9
10// Throw if cancellation requested
11token.ThrowIfCancellationRequested(); // Throws OperationCanceledException
12
13// Check if token can be canceled
14bool canBeCanceled = token.CanBeCanceled; // false for CancellationToken.None

Passing Token to Async Methods

Most async APIs accept a CancellationToken:

csharp
1async Task FetchDataAsync(CancellationToken cancellationToken)
2{
3 // Pass to HTTP client
4 var response = await httpClient.GetAsync(url, cancellationToken);
5
6 // Pass to stream read
7 await stream.ReadAsync(buffer, cancellationToken);
8
9 // Pass to Task.Delay
10 await Task.Delay(1000, cancellationToken);
11}

Handling OperationCanceledException

csharp
1try
2{
3 await LongRunningOperationAsync(cts.Token);
4}
5catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
6{
7 // Expected cancellation - handle gracefully
8 Console.WriteLine("Operation canceled by user");
9}
10catch (OperationCanceledException)
11{
12 // Unexpected cancellation (different token)
13 throw;
14}

CancellationToken.None

For APIs that require a token but you don't need cancellation:

csharp
1// Pass when you don't want cancellation
2await ProcessAsync(CancellationToken.None);
3
4// Equivalent to default
5await ProcessAsync(default);

Disposing CancellationTokenSource

Always dispose when done:

csharp
1// Manual disposal
2var cts = new CancellationTokenSource();
3try
4{
5 await DoWorkAsync(cts.Token);
6}
7finally
8{
9 cts.Dispose();
10}
11
12// Using statement (preferred)
13using var cts = new CancellationTokenSource();
14await DoWorkAsync(cts.Token);

Example: User-Initiated Cancellation

csharp
1class DataProcessor
2{
3 private CancellationTokenSource? _cts;
4
5 public async Task StartProcessingAsync()
6 {
7 _cts = new CancellationTokenSource();
8
9 try
10 {
11 await ProcessAllDataAsync(_cts.Token);
12 Console.WriteLine("Processing completed");
13 }
14 catch (OperationCanceledException)
15 {
16 Console.WriteLine("Processing was canceled");
17 }
18 finally
19 {
20 _cts.Dispose();
21 _cts = null;
22 }
23 }
24
25 public void CancelProcessing()
26 {
27 _cts?.Cancel();
28 }
29}
30
31// Usage
32var processor = new DataProcessor();
33var task = processor.StartProcessingAsync();
34
35// Later, user clicks cancel
36processor.CancelProcessing();
37
38await task;

Key Takeaways

  • Cancellation is cooperative - operations must check the token
  • CancellationTokenSource controls cancellation
  • CancellationToken is passed to operations
  • Use ThrowIfCancellationRequested() or check IsCancellationRequested
  • Catch OperationCanceledException to handle cancellation
  • Always dispose CancellationTokenSource
  • Use CancellationToken.None when cancellation isn't needed