Ok, so I understand how to do Task cancellations using CancellationTokenSource
. it appears to me that the Task
type "kind of" handles this exception automatically - it sets the Task
's Status
to Cancelled.
Now you still actually have to handle the OperationCancelledException
. Otherwise the exception bubbles up to Application.UnhandledException
. The Task itself kind of recognizes it and does some handling internally, but you still need to wrap the calling code in a try block to avoid the unhandled exception. Sometimes, this seems like unnecessary code. If the user presses cancel, then cancel the Task
(obviously the task itself needs to handle it too). I don't feel like there needs to be any other code requirement. Simply check the Status
property for the completion status of the task.
Is there any specific reason for this from a language design point of view? Is there any other way to set the Status
property to cancelled?
You can set a Task's status to cancelled without a CancellationToken
if you create it using TaskCompletionSource
var tcs = new TaskCompletionSource();
var task = tcs.Task;
tcs.SetCancelled();
Other than that you can only cancel a running Task
with a CancellationToken
You only need to wrap the calling code in a try/catch block where you're asking for the result, or waiting for the task to complete - those are the situations in which the exception is thrown. The code creating the task won't throw that exception, for example.
It's not clear what the alternative would be - for example:
string x = await GetTaskReturningString();
Here we never have a variable referring to the task, so we can't explicitly check the status. We'd have to use:
var task = GetTaskReturningString();
string x = await task;
if (task.Status == TaskStatus.Canceled)
{
...
}
... which is not only less convenient, but also moves the handling of the "something happened" code into the middle of the normal success path.
Additionally, by handling cancellation with an exception, if you have several operations, you can put all the handling in one catch block instead of checking each task separately:
try
{
var x = await GetFirstTask();
var y = await GetSecondTask(x);
}
catch (OperationCanceledException e)
{
// We don't care which was canceled
}
The same argument applies for handling cancellation in one place wherever in the stack the first cancellation occurred - if you have a deep stack of async methods, cancellation in the deepest method will result in the top-most task being canceled, just like normal exception propagation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With