📌 Introduction
Cancellation is a critical concept in modern task-based programming. Whether you're building responsive user interfaces or managing long-running background operations, enabling cancellation allows your application to be more robust, resource-conscious and user-friendly.
In this article, we’ll explore how to implement cooperative cancellation in C# using the CancellationToken
and CancellationTokenSource
classes, demonstrate real-world examples and highlight best practices to avoid leaving your application in an inconsistent state.
🚦 Why Cancellation Matters
Task cancellation is useful in several common scenarios:
Gracefully stopping a task that is no longer needed
Releasing critical or limited resources
Enhancing application responsiveness and user experience
Rather than forcibly terminating a task, C# promotes a cooperative cancellation model. This ensures both the task and the calling code can coordinate the cancellation logic, maintaining application integrity.
🧰 The Core Cancellation Components in C#
Two key components drive task cancellation:
CancellationTokenSource
: Signals a cancellation request and produces aCancellationToken
.CancellationToken
: Passed to the task so it can check for cancellation requests.
Example:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
To link this token with a task:
var printTask = Task.Run(() => {
// Your task logic here
}, token);
👤 User-Initiated Cancellation
🧪 Example 1: Cooperative Exit with Return
In this example, we create a task that prints numbers from 0 to 99 and can be canceled by user input:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var printTask = Task.Run(() => {
for (int i = 0; i < 100; i++) {
if (token.IsCancellationRequested) {
Console.WriteLine("Cancelling the print activity.");
return;
}
Console.WriteLine($"{i}");
Thread.Sleep(500);
}
}, token);
Console.WriteLine("Enter c to cancel the task.");
if (Console.ReadKey().KeyChar == 'c') {
Console.WriteLine("\nRaising the cancellation request.");
tokenSource.Cancel();
}
printTask.Wait();
Console.WriteLine($"The final status of printTask is: {printTask.Status}");
✅ Output
0
1
2
c
Raising the cancellation request.
Cancelling the print activity.
The final status of printTask is: RanToCompletion
Even though the task was canceled, the final status is RanToCompletion
because the task ended via return, not via exception.
⚠️ Alternative: Exception-Based Cancellation
🧪 Example 2: Using OperationCanceledException
To explicitly mark a task as canceled:
if (token.IsCancellationRequested) {
Console.WriteLine("Cancelling the print activity.");
throw new OperationCanceledException(token);
}
✅ Output
Cancelling the print activity.
Caught: TaskCanceledException, Message: A task was canceled.
The final status of printTask is: Canceled
This approach is preferred if you want the task status to be Canceled
.
⏱️ Timeout Cancellation
Instead of waiting indefinitely, you can schedule cancellation:
tokenSource.CancelAfter(2000);
This triggers a cancellation request after 2000 milliseconds, unless the user cancels earlier. You can combine both approaches.
🛰️ Monitoring Cancellation Events
C# offers several mechanisms to monitor when a cancellation occurs:
🔔 Using Register
token.Register(() => {
Console.WriteLine("Cancelling the print activity. [Using event subscription]");
});
🧱 Using WaitHandle.WaitOne
Task.Run(() => {
token.WaitHandle.WaitOne();
Console.WriteLine("Cancelling the print activity. [Using WaitHandle]");
});
Both methods allow you to observe and react when the cancellation is requested.
🧪 Demonstration: Monitoring in Action
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
token.Register(() => {
Console.WriteLine("Cancelling the print activity. [Using event subscription]");
});
var printTask = Task.Run(() => {
for (int i = 0; i < 100; i++) {
token.ThrowIfCancellationRequested();
Console.WriteLine($"{i}");
Thread.Sleep(500);
}
}, token);
Task.Run(() => {
token.WaitHandle.WaitOne();
Console.WriteLine("Cancelling the print activity. [Using WaitHandle]");
});
Console.WriteLine("Enter c to cancel the task.");
if (Console.ReadKey().KeyChar == 'c') {
Console.WriteLine("\nTask cancellation requested.");
tokenSource.Cancel();
}
while (!printTask.IsCompleted) { }
Console.WriteLine($"The final status of printTask is: {printTask.Status}");
🧩 Multiple Cancellation Tokens
Sometimes, multiple conditions may trigger cancellation. In that case, use a linked token.
var normalCancellation = new CancellationTokenSource();
var unexpectedCancellation = new CancellationTokenSource();
var compositeToken = CancellationTokenSource.CreateLinkedTokenSource(
normalCancellation.Token,
unexpectedCancellation.Token
);
var printTask = Task.Run(() => {
for (int i = 0; i < 100; i++) {
compositeToken.Token.ThrowIfCancellationRequested();
Console.WriteLine($"{i}");
Thread.Sleep(500);
}
}, compositeToken.Token);
You can cancel from any source:
normalCancellation.Cancel(); // User input
unexpectedCancellation.Cancel(); // Emergency trigger
🧾 Summary
Task cancellation in C# enables cooperative control over long-running operations, allowing developers to:
Gracefully stop tasks via user interaction
Automate cancellation using timeouts
Monitor and respond to cancellation via event registration
Combine multiple cancellation sources for complex scenarios
By designing your applications with proper cancellation handling, you ensure responsiveness, reliability and clean resource management. All essential traits of a modern, professional .NET application.