When you are building complex tasks, one of the features that you will take advantage of to optimize the performance is threading. Threading offers tools and mechanisms to execute multiple threads concurrently in a single process.
Using threading, we can allow multiple threads to share the same data space with the main thread and thus provide a communication between them. This comes with potential issues such as data races but that is beyond the scope of this post.
In this guide, we will talk about the CancellationTokenSource class in C#. This class provides us to send a CancellationToken signal that it should be cancelled. A CancellationToken is a struct that propagates the notification that the specified operations should be cancelled.
NOTE: Although we do provide some fundamental concepts, we assume that you have a C# knowledge and you are not new to threading and async functionality.
The Basics
The CancellationTokenSource class is an incredible component when working with multithreaded applications as it allows us to cancel the tasks. For example, if you have a long-running task that is causing the performance issues or is unresponsive, this class provides all the mechanisms to cancel such a task.
How It Works
The CancellationTokenSource works in conjunction with the CancellationToken struct. It starts by generating a token which is passed to the tasks and threads.
Once the source’s “Cancel” method is run, all the associated tokens receive a signal to initiate the cancellation. The tasks that receive this signal will then respond by terminating the execution.
The syntax is as follows:
Example Usage:
To best understand how the CancellationTokenSource works, let us look at a basic implementation of threading.
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task longRunningTask = Task.Run(() =>
{
for (int i = 0; i < 5; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Task cancelled before completion.");
return;
}
Console.WriteLine(i);
Thread.Sleep(1000);
}
Console.WriteLine("Task completed successfully.");
}, token);
Thread.Sleep(2000);
cts.Cancel();
await longRunningTask;
}
}
In the given example, we simulate a long running task that sleeps for 1000 seconds.
We also create a CancellationTokenSource object and retrieve the CancellationToken from the source.
We then pass the token to the long-running task as simulated by the “for” loop and the sleep function.
Once the task has been running for 2 seconds, we call the “Cancel” method from the CancellationTokenSource. The task then checks for the IsCancellationRequested property of the token and terminates the task early if the value is true.
Output:
1
Task cancelled before completion.
Working with Async Methods
The most common use case of the CancellationTokenSource is when dealing with async methods. Luckily, the implementation is simple and we can just implement it in the method as shown in the following example:
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
await longTask("task 1", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancellation requested. task terminated!");
}
}
static async Task longTask(string task, CancellationToken token)
{
for (int i = 0; i < 5; i++)
{
if (token.IsCancellationRequested)
{
throw new OperationCanceledException($"{task} was cancelled before its completion.");
}
Console.WriteLine($"{task} is in progress. Step {i + 1}.");
await Task.Delay(1000);
}
Console.WriteLine($"{task} completed successfully.");
}
}
Let us break down the given code.
In the previous code, we define an async method called “longTask”. The method accepts a CancellationToken as a parameter.
The token allows us to check if the cancellation is requested on each iteration of the task.
If the token indicates that the method has received a cancellation request, we throw an OperationCanceledException and terminate the task.
In the main method, we await for the longTask method. If an OperationCanceledException occurs, we display a custom cancellation method.
Automatic Cancellation with Timeout
You might notice that the long running task runs successfully unless an exception arises. In some cases, you might want to terminate the task after a specific duration has elapsed. This is where the CancelAfter() method comes into play.
This method allows us to specify a timeout duration that forces the task to cancel once the timeout has expired. Consider the following example code:
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(2000);
try
{
await longTask("task 1", cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("timeout. task terminated!");
}
}
static async Task longTask(string task, CancellationToken token)
{
for (int i = 0; i < 5; i++)
{
if (token.IsCancellationRequested)
{
throw new OperationCanceledException($"{task} was cancelled before its completion.");
}
Console.WriteLine($"{task} is in progress. Step {i + 1}.");
await Task.Delay(1000);
}
Console.WriteLine($"{task} completed successfully.");
}
}
In the given example, we simply call the cts.CancelAfter() method and pass the duration, in milliseconds, that we wish to wait before terminating the task.
The previous code should display an output as follows:
task 1 is in progress. Step 2.
timeout . task terminated!
There you have it!
Conclusion
In this tutorial, we dived into the world of threading and async programming in C# to learn about how to cancel the tasks using the CancellationTokenSource. It is good to keep in mind that this tutorial only scratches the surface. You can consider the documentation for implementation and more details.