Handling Cancellation in Asynchronous Operations
Asynchronous operations can sometimes take a long time or become unnecessary. .NET provides robust mechanisms for handling cancellation, allowing you to signal to an ongoing operation that it should stop processing.
The Role of CancellationToken and CancellationTokenSource
The core of cancellation in .NET is the pair of CancellationTokenSource and CancellationToken.
CancellationTokenSource: This object is responsible for initiating the cancellation request. You create aCancellationTokenSource, and then obtain its associatedCancellationToken.CancellationToken: This token is passed to the asynchronous operation. The operation periodically checks theIsCancellationRequestedproperty of the token. If it's true, the operation should clean up and stop.
Here's a basic pattern:
using System;
using System.Threading;
using System.Threading.Tasks;
public class AsyncOperationWithCancellation
{
public async Task DoWorkAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Operation started...");
for (int i = 0; i < 10; i++)
{
// Check for cancellation before starting a new iteration
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine($"Processing step {i + 1}...");
await Task.Delay(500, cancellationToken); // Task.Delay also respects CancellationToken
}
Console.WriteLine("Operation completed successfully.");
}
public async Task RunOperation()
{
using (var cts = new CancellationTokenSource())
{
var operationTask = DoWorkAsync(cts.Token);
// Simulate cancelling after a few seconds
await Task.Delay(2000);
Console.WriteLine("Requesting cancellation...");
cts.Cancel();
try
{
await operationTask;
Console.WriteLine("Operation task finished without exception.");
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was successfully cancelled.");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
}
}
}
Checking for Cancellation
There are two primary ways an asynchronous method can respond to a cancellation request:
- Polling: Periodically check the
CancellationToken.IsCancellationRequestedproperty. If it returnstrue, the method should clean up its resources and return early. - Throwing Exception: Use the convenient
CancellationToken.ThrowIfCancellationRequested()method. This method throws anOperationCanceledExceptionif cancellation has been requested. This is often preferred as it clearly signals the intent.
Many .NET asynchronous APIs, like Task.Delay, HttpClient.GetAsync, and others, accept a CancellationToken directly. When passed, these methods will automatically monitor the token and throw OperationCanceledException if cancellation is requested during their execution.
CancellationToken down the call chain to operations that can potentially take a long time. This allows cancellation to propagate effectively.
Handling Cancellation Exceptions
When CancellationToken.ThrowIfCancellationRequested() is called or when a built-in API detects a cancellation request, an OperationCanceledException is thrown. Your code that calls the cancellable operation should be prepared to catch this specific exception.
try
{
await SomeCancellableOperationAsync(cancellationToken);
}
catch (OperationCanceledException)
{
// Handle the cancellation gracefully.
// This is the expected outcome when cancellation is requested.
Console.WriteLine("The operation was cancelled as requested.");
}
catch (Exception ex)
{
// Handle other potential exceptions.
Console.WriteLine($"An error occurred: {ex.Message}");
}
Note that an OperationCanceledException thrown due to cancellation will have its CancellationToken property set to the token that was used to request the cancellation. This can be useful for distinguishing between different cancellation requests if you have multiple tokens in play.
Advanced Scenarios
- Linking Tokens: You can link multiple
CancellationTokenSourceobjects together so that cancelling one cancels them all. - Registering Callbacks: You can register an action to be executed when a token is cancelled, which can be useful for performing cleanup logic.
Mastering asynchronous cancellation is crucial for building responsive and robust applications in .NET.