Cancellation Tokens
Vaibhav • September 11, 2025
In the previous article, we explored how exceptions behave in asynchronous code and how to catch, log, and propagate them properly. But what if you want to stop an async operation before it completes? Maybe the user navigated away, or the data is no longer needed. That’s where cancellation tokens come in. They allow you to cancel long-running tasks cooperatively and cleanly.
In this article, we’ll learn how cancellation works in C#, how to use CancellationToken
and CancellationTokenSource
,
and how to design async methods that respond to cancellation requests. We’ll also cover best practices, common
mistakes, and how cancellation interacts with await
, Task
, and exception handling.
Why Cancellation Matters
Async operations often involve waiting-reading files, querying databases, calling APIs. If the operation becomes irrelevant (e.g., the user closes the app or switches tabs), continuing the task wastes resources and may degrade performance. Cancellation lets you stop the operation early and reclaim control.
Cancellation is especially important in UI applications, background services, and server-side code where responsiveness and scalability matter. It’s also a key part of designing robust and user-friendly APIs.
Cancellation in C# is cooperative. The task must check for cancellation and exit gracefully. The runtime does not forcibly kill the thread or task.
Introducing CancellationToken and CancellationTokenSource
Cancellation in .NET is managed using two types:
CancellationTokenSource
- creates and controls the cancellation signalCancellationToken
- passed to tasks and methods to observe cancellation
Here’s a basic example:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task t = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("Working...");
Thread.Sleep(500);
}
Console.WriteLine("Cancelled!");
}, token);
Thread.Sleep(2000);
cts.Cancel();
This code starts a task that checks the token periodically. After 2 seconds, we call Cancel()
on the source, which signals the token. The task sees the signal and
exits.
Using CancellationToken in Async Methods
To support cancellation in an async method, add a CancellationToken
parameter
and pass it to cancellable operations like Task.Delay
, Stream.ReadAsync
, or HttpClient.SendAsync
.
async Task LoadDataAsync(CancellationToken token)
{
await Task.Delay(1000, token);
Console.WriteLine("Data loaded");
}
If the token is cancelled before or during the delay, Task.Delay
throws a TaskCanceledException
. You can catch this exception to handle cancellation
gracefully.
try
{
await LoadDataAsync(token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
This pattern ensures that your method exits cleanly and doesn’t leave resources hanging.
ThrowIfCancellationRequested
Not all operations support cancellation tokens directly. In such cases, you can check the token manually and throw an exception to exit early.
async Task ProcessAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
string data = await File.ReadAllTextAsync("data.txt");
token.ThrowIfCancellationRequested();
Console.WriteLine("Processing complete");
}
This method checks the token before and after reading the file. If cancellation is requested, it throws OperationCanceledException
, which can be caught by the caller.
Always check the token before doing expensive work or after long-running operations. This keeps your method responsive to cancellation.
Linking Tokens
Sometimes you want to combine multiple cancellation sources-like a global timeout and a user-triggered cancel.
You can use CancellationTokenSource.CreateLinkedTokenSource
to merge them.
CancellationTokenSource userCts = new CancellationTokenSource();
CancellationTokenSource timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
userCts.Token, timeoutCts.Token);
await DoWorkAsync(linkedCts.Token);
This token is cancelled if either the user cancels or the timeout expires. It’s a powerful way to compose cancellation logic.
Handling Cancellation Exceptions
When a cancellable operation is aborted, it throws OperationCanceledException
.
This is a subclass of Exception
and should be handled separately from other
errors.
try
{
await Task.Delay(1000, token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled by user");
}
catch (Exception ex)
{
Console.WriteLine("Other error: " + ex.Message);
}
This pattern ensures that cancellation is treated as a normal control flow, not an error. You can also check
token.IsCancellationRequested
before catching to confirm the source.
Cancellation in UI Applications
In UI apps, cancellation is often triggered by user actions-like clicking a cancel button or navigating away.
You can store the CancellationTokenSource
in a field and cancel it when needed.
CancellationTokenSource _cts;
async void StartButton_Click(object sender, EventArgs e)
{
_cts = new CancellationTokenSource();
try
{
await LoadDataAsync(_cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled by user");
}
}
void CancelButton_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
This pattern gives users control over long-running operations and improves responsiveness.
Cancellation in Server Applications
In ASP.NET Core, cancellation tokens are built into the request pipeline. You can access HttpContext.RequestAborted
to detect when the client disconnects or cancels the
request.
public async Task GetData(CancellationToken cancellationToken)
{
string data = await _service.LoadAsync(cancellationToken);
return Ok(data);
}
This ensures that your server doesn’t waste resources on abandoned requests. It also improves scalability under load.
Common Mistakes
Cancellation is powerful but easy to misuse. Here are some common mistakes:
- Not passing the token to cancellable operations
- Swallowing
OperationCanceledException
without logging - Using
async void
and losing cancellation control - Not disposing
CancellationTokenSource
(useusing
orDispose()
)
You can cancel a task before it starts by calling Cancel()
on
the source. The task will never run, and its status will be Canceled
.
Summary
Cancellation tokens are a key part of writing responsive and scalable async code. They allow you to abort
operations cleanly, reclaim resources, and improve user experience. You’ve learned how to use CancellationToken
and CancellationTokenSource
,
how to pass tokens to async methods, how to handle cancellation exceptions, and how to design cancellable APIs
for UI and server applications.
In the next article, we’ll explore Task Parallelism, where we’ll learn how to run multiple
tasks concurrently, coordinate their results, and build high-performance parallel workflows using the Task
class.