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
csharp1// CancellationTokenSource - Controls cancellation2var cts = new CancellationTokenSource();34// CancellationToken - Passed to operations5CancellationToken token = cts.Token;67// Request cancellation8cts.Cancel();910// Check if cancellation requested11bool 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
csharp1async Task ProcessDataAsync(CancellationToken cancellationToken)2{3 for (int i = 0; i < 1000; i++)4 {5 // Check for cancellation periodically6 cancellationToken.ThrowIfCancellationRequested();78 await ProcessItemAsync(i);9 }10}1112// Usage13var cts = new CancellationTokenSource();1415try16{17 await ProcessDataAsync(cts.Token);18}19catch (OperationCanceledException)20{21 Console.WriteLine("Operation was canceled");22}
Creating CancellationTokenSource
csharp1// Basic creation2var cts = new CancellationTokenSource();34// With timeout - auto-cancels after delay5var ctsWithTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));67// Cancel after a delay8var cts2 = new CancellationTokenSource();9cts2.CancelAfter(TimeSpan.FromSeconds(10));
CancellationToken Methods
csharp1CancellationToken token = cts.Token;23// Check if cancellation requested (doesn't throw)4if (token.IsCancellationRequested)5{6 // Clean up and exit7 return;8}910// Throw if cancellation requested11token.ThrowIfCancellationRequested(); // Throws OperationCanceledException1213// Check if token can be canceled14bool canBeCanceled = token.CanBeCanceled; // false for CancellationToken.None
Passing Token to Async Methods
Most async APIs accept a CancellationToken:
csharp1async Task FetchDataAsync(CancellationToken cancellationToken)2{3 // Pass to HTTP client4 var response = await httpClient.GetAsync(url, cancellationToken);56 // Pass to stream read7 await stream.ReadAsync(buffer, cancellationToken);89 // Pass to Task.Delay10 await Task.Delay(1000, cancellationToken);11}
Handling OperationCanceledException
csharp1try2{3 await LongRunningOperationAsync(cts.Token);4}5catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)6{7 // Expected cancellation - handle gracefully8 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:
csharp1// Pass when you don't want cancellation2await ProcessAsync(CancellationToken.None);34// Equivalent to default5await ProcessAsync(default);
Disposing CancellationTokenSource
Always dispose when done:
csharp1// Manual disposal2var cts = new CancellationTokenSource();3try4{5 await DoWorkAsync(cts.Token);6}7finally8{9 cts.Dispose();10}1112// Using statement (preferred)13using var cts = new CancellationTokenSource();14await DoWorkAsync(cts.Token);
Example: User-Initiated Cancellation
csharp1class DataProcessor2{3 private CancellationTokenSource? _cts;45 public async Task StartProcessingAsync()6 {7 _cts = new CancellationTokenSource();89 try10 {11 await ProcessAllDataAsync(_cts.Token);12 Console.WriteLine("Processing completed");13 }14 catch (OperationCanceledException)15 {16 Console.WriteLine("Processing was canceled");17 }18 finally19 {20 _cts.Dispose();21 _cts = null;22 }23 }2425 public void CancelProcessing()26 {27 _cts?.Cancel();28 }29}3031// Usage32var processor = new DataProcessor();33var task = processor.StartProcessingAsync();3435// Later, user clicks cancel36processor.CancelProcessing();3738await task;
Key Takeaways
- Cancellation is cooperative - operations must check the token
CancellationTokenSourcecontrols cancellationCancellationTokenis passed to operations- Use
ThrowIfCancellationRequested()or checkIsCancellationRequested - Catch
OperationCanceledExceptionto handle cancellation - Always dispose
CancellationTokenSource - Use
CancellationToken.Nonewhen cancellation isn't needed