Daily Knowledge Drop
When cancelling tasks using a CancellationTokenSource
, rather than do a soft cancellation, an OperationCanceledException
should be thrown.
Cancellation Token
A quick summary of cancellation tokens - they "enable cooperative cancellation between threads, thread pool work items or Task objects".
Basically cancellation tokens are instantiated outside a particular thread, and then passed into the thread, to allow for cancellation from outside the thread.
Examples
In each of the below examples, there is a "long" running process which runs for 10 seconds. A cancellation token is passed in and checked every 1 second to see if a cancellation has been requested - if requested, then an action will be perform to abort/cancel the long running process.
Soft cancellation
The first example will show how to do a "soft cancellation".
Here, if a cancellation is requested, the developer will decide how to manually cause the method to end its execution.
var cts = new CancellationTokenSource();
var token = cts.Token;
// start the long running process
var longProcessTask = Task.Run(async () =>
{
for (var i = 0; i < 10; i++)
{
// If the token has been requested to cancel, break
/ out of the loop causing the long processing and
// effectively cause the long running task to finish
if (token.IsCancellationRequested)
break; // exit from the loop
Console.WriteLine("Processing...");
await Task.Delay(1000);
}
}, token);
// If the user presses a key, the token is cancelled
Console.ReadKey();
cts.Cancel();
await longProcessTask;
Console.WriteLine($"Task is completed: {longProcessTask.IsCompleted}");
The problem with this approach, is that there is no indication if the task actually finished to completion or not
.
If the longProcessTask was required to finish, before another process was to kick off - we do not know if the longProcessTask finished due to executing to its end, or if it finished due to be cancelled early.
OperationCanceledException
Rather than a "soft cancellation, a OperationCanceledException
should be used. This provides a clear way of determining if the task was cancelled or finished successfully.
var cts = new CancellationTokenSource();
var token = cts.Token;
// start the long running process
var longProcessTask = Task.Run(async () =>
{
for (var i = 0; i < 10; i++)
{
// If the token has been requested to cancel,
// throw an _OperationCanceledException_
if (token.IsCancellationRequested)
throw new OperationCanceledException();
Console.WriteLine("Processing...");
await Task.Delay(1000);
}
}, token);
// The longProcessTask is awaited inside a try-catch block.
try
{
Console.ReadKey();
cts.Cancel();
await longProcessTask;
Console.WriteLine($"Task is completed: {longProcessTask.IsCompleted}");
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} " +
$"thrown with message: {e.Message}");
}
With this method, when task is cancelled we can now tell vs the task actually finishing to completion.
ThrowIfCancellationRequested
There is a slight improvement which can be made on the above, which is the recommended way of dealing with a cancelled task, and that is to use the ThrowIfCancellationRequested
method on a token.
Looking at the source code for the method one can see that its basically doing the same as the above, just wrapped up neatly into a method.
var cts = new CancellationTokenSource();
var token = cts.Token;
// start the long running process
var longProcessTask = Task.Run(async () =>
{
for (var i = 0; i < 10; i++)
{
// check if a cancellation has been requested
token.ThrowIfCancellationRequested();
Console.WriteLine("Processing...");
await Task.Delay(1000);
}
}, token);
try
{
Console.ReadKey();
cts.Cancel();
await longProcessTask;
Console.WriteLine($"Task is completed: {longProcessTask.IsCompleted}");
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} " +
$"thrown with message: {e.Message}");
}
Notes
We've looked at a number of ways to cancel a task. The soft cancellation
, while successfully cancellation a task, has some limitations and the better option is to use a OperationCanceledException
, facilitated by the ThrowIfCancellationRequested
method.
References
Daily Drop 43: 01-04-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.