The CancellationToken and its related types provide developers a unified way to gracefully cancel tasks, threads, or asynchronous operations. In this blog post, we’ll explore how CancellationToken works and its best practices.
What is a CancellationToken?
A CancellationToken is a struct that represents a notification to cancel an operation. It’s part of the Task Parallel Library (TPL) and is particularly useful in async/await scenarios where you must cleanly cancel long-running operations.
Basics of CancellationToken
To initiate and signal a cancellation, you use the CancellationTokenSource class, which acts as the controller.
using var cts = new CancellationTokenSource();
await LongAwaitedOperation(cts.Token);
The CancellationTokenSource class implements an IDisposable interface. So remember to call the Dispose method or create the CancellationTokenSource with using keyword to call the Dispose method automatically to free any unmanaged resources it holds.
Canceling Operation
There are three methods to cancel the operation. All methods request the operation cancellation. You can request the operation cancellation synchronously, asynchronously, and after some time.
// Notify the operation to cancel synchronously
cts.Cancel();
// Notify the operation to cancel asynchronously
await cts.CancelAsync();
// Notify the operation to cancel after 5 seconds
cts.CancelAfter(5000);
You can define the timeout when the operation is canceled automatically.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
Listening the Cancellation Requests
Polling
You can listen for a cancellation request by periodically polling the value of the IsCancellationRequested property.
async Task LongAwaitedOperation(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Operation cancelled");
break;
}
await Task.Delay(1000, token);
}
}
You can also use the ThrowIfCancellationRequested that throws the OperationCanceledException when the cancellation is requested.
async Task LongAwaitedOperation(CancellationToken token)
{
try
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(1000, token);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation cancelled");
}
}
Callback
In some operations, you cannot use polling to check if the operation has been canceled. For such scenarios, you can register a callback.
async Task LongAwaitedOperation(CancellationToken token)
{
HttpClient client = new();
token.Register(() => {
client.CancelPendingRequests();
Console.WriteLine("Operation cancelled");
});
var result = await client.GetStringAsync("https://www.example.com");
}
Linking Multiple Cancellation Tokens
Sometimes, you need to cancel an operation based on multiple conditions. For example, you want to specify an operation timeout but have external CancellationToken to handle.
async Task DoWorkWithMultipleTokensAsync(CancellationToken externalToken)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(externalToken, timeoutCts.Token);
try
{
await DoWorkAsync(linkedCts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled or timed out");
}
}
Best Practices
- Respect CancellationToken: Don’t ignore the CancellationToken as a method parameter.
- Dispose CancellationTokenSource: Use using keyword when creating CancellationTokenSource.
- Don’t Create Unnecessary Sources: If you pass through a token, don’t create a new source.
- Don’t Ignore Cancellation: Always handle OperationCanceledException appropriately.
Conclusion
CancellationToken is an essential tool for building responsive and reliable .NET applications. By following these best practices, you can implement robust application cancellation support, leading to better resource management and user experience.