1. Introduction
When you start an asynchronous (or long-running) operation, the user (or other code) may suddenly decide: "Wait! Not needed anymore! Stop!". For example, the user decided to cancel downloading a huge file, closed the program window, or changed their mind about searching a big database. Without cancellation support your program may keep running and waste resources — not the best way to be kind to the user and the machine.
Typical cancellation scenarios:
- Cancelling a file download or cancelling data upload.
- Quickly aborting complex data processing on user request.
- When a long task becomes irrelevant due to a sudden pause/stop.
Cancellation is your secret ingredient for friendly, responsive, and resource-conscious apps.
How to cancel asynchronous operations in .NET?
In .NET the concept of a "cancellation token" (CancellationToken) is used to cancel long-running tasks. It's a special object that's passed to all parts of the task. If someone requests cancellation — the token immediately signals that to all interested parts of the program. Think of it like a red flag: whoever sees it first should stop.
In .NET this mechanism is implemented with two key classes:
- CancellationTokenSource — creates and manages the cancellation token.
- CancellationToken — passed into async methods so they can be cancelled.
Important: the cancellation token itself does not interrupt code execution, it only "signals" — your application decides when and how to react to that signal.
2. Creating a cancellation token and cancelling a task
Let's see how this works with a simple example (we'll build this into our training console app).
Example: simple asynchronous operation with cancellation
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DemoApp
{
class Program
{
static async Task Main()
{
// Create a cancellation token source
CancellationTokenSource cts = new CancellationTokenSource();
// Start an asynchronous task
Task longRunningTask = DoWorkAsync(cts.Token);
Console.WriteLine("Press any key to cancel the operation...");
Console.ReadKey();
// Request cancellation
cts.Cancel();
try
{
await longRunningTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("The operation was cancelled!");
}
}
// Asynchronous method that supports cancellation
static async Task DoWorkAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
// Check the cancellation signal
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine($"Executing step {i + 1}/10...");
await Task.Delay(1000); // 1 second delay
}
Console.WriteLine("Operation completed successfully!");
}
}
}
How does it work?
- We create a CancellationTokenSource (cts), from which we get the token (cts.Token).
- We pass this token into our asynchronous operation.
- Inside DoWorkAsync() we regularly check the token for cancellation using ThrowIfCancellationRequested(). If the user asks to cancel the task — the method will throw an OperationCanceledException, and the task will stop.
- In Main() we wait for any key press and call cts.Cancel() to "signal" the need to stop the operation.
If you don't check cancellationToken.IsCancellationRequested or call ThrowIfCancellationRequested(), your task will keep running as if nothing happened — the token is just an informational flag.
3. CancellationToken: how it works? And a bit of magic
A cancellation token is an object that you can easily pass between methods and tasks. That gives a lot of flexibility:
- The same token can be used in multiple async and sync operations.
- You can organize a "group" cancellation for all tasks if several operations use a token from the same CancellationTokenSource.
- The cancellation token is unobtrusive: even if you ignore it, the code continues to work as before.
Managing cancellation: where and how to check the token?
You should check whether the "cancellation flag" has been raised in places where it makes logical sense: inside loops, at each step of long processing, when switching between stages, etc.
// At any check point
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Operation cancelled! Exiting...");
return;
}
// Or like this (short and throwing)
cancellationToken.ThrowIfCancellationRequested();
Typically people use ThrowIfCancellationRequested() — it throws a special exception which can be caught by the caller.
4. Asynchronous methods in the standard library
Many .NET classes and methods (especially async ones) support CancellationToken out of the box. You should use them to "properly" stop operations.
Here is an example with asynchronous file reading:
using System.IO;
using System.Threading;
using System.Threading.Tasks;
class FileDemo
{
public static async Task ReadFileWithCancelAsync(string filePath, CancellationToken cancellationToken)
{
using FileStream stream = File.OpenRead(filePath);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
{
// Process the data...
// If the cancellation token was requested, ReadAsync will itself throw OperationCanceledException
}
}
}
Read more about FileStream.ReadAsync and CancellationToken in the official docs.
5. Useful nuances
Timeout is also cancellation!
You can automatically cancel operations after a given time. For that you can "program" the cancellation token:
// Create a CancellationTokenSource with a timeout (for example, 5 seconds)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
After 5 seconds the token will be automatically "raised", and all operations using it will stop at the next check. This is handy if you don't want to wait forever.
What happens when cancellation is requested?
When you call Cancel() on the token source, all methods that use this token and check its state will learn about the change. But if your code doesn't check the token — cancellation won't happen.
A common mistake: forgetting to pass the token into all async and long-running calls. Then part of the operation will be cancelled, and part will continue as if nothing happened.
Visualization: how cancellation flows
sequenceDiagram
participant Main as Main thread
participant CTS as CancellationTokenSource
participant Task as Async task
Main->>CTS: creates CTS, gets token
Main->>Task: passes token into async task
Note over Task: Task periodically checks the token
Main->>CTS: calls Cancel()
CTS-->>Task: token gets "cancelled" status
Task-->>Main: throws OperationCanceledException
Main->>Main: catches the exception and finishes
Where is cancellation used?
- Async downloads and server requests: can be cancelled on flaky network or when the user gets tired.
- Large computations: can be stopped on timeout or on user request.
- Network operations, file work, processing big collections in the background.
That wraps up the intro to cancelling asynchronous operations — now your app can be not only fast, but considerate too!
6. Tips and common mistakes
Don't forget to pass the cancellation token into all methods and calls that support cancellation. If you miss it somewhere — the operation can "hang" and not stop.
Check the token regularly — especially in long loops, file processing, or big uploads/downloads. Use IsCancellationRequested or ThrowIfCancellationRequested().
Don't try to forcibly kill a thread or task from the outside: the cancellation token is a polite request to stop, not a club to beat the thread with.
Standard library functions like ReadAsync, Delay, HttpClient.SendAsync and many others already support cancellation via a token. Use that!
When handling cancellation, catch specifically OperationCanceledException — it's the special exception that indicates a proper cancellation on request.
GO TO FULL VERSION