Given a method signature like
Taks<int> ComputeAsync(..., CancellationToken cancellationToken)
, one would expect the returned task to complete as
RanToCompletion
(and theResult
set) orFaulted
(andException
set) orCanceled
if thecancellationToken
requested cancellation
When implementing the method as an async
method, how can this be achieved?
By testing I found out that throwing an OperationCanceledException
from within the async method apparently completes the task in the Canceled
status (regardless of what token is wrapped in the exception, regardless even of whether the token IsCancellationRequested
):
var tasks = new[]
{
Task.FromResult(42),
Task.FromException<int>(new Exception("Boom!")),
Task.FromCanceled<int>(new CancellationToken(true)),
Task.FromException<int>(new OperationCanceledException(new CancellationToken(true)))
};
async Task<int> Await(Task<int> task) => await task;
foreach (var t in tasks)
Console.WriteLine($"{t.Status} - {Await(t).Status}");
Output:
RanToCompletion - RanToCompletion
Faulted - Faulted
Canceled - Canceled
Faulted - Canceled
However, I can't seem to find any documentation nor other information on the above behavior. Can it be relied upon across framework versions?
If the answer is yes, then cancellationToken.ThrowIfCancellationRequested()
would do the right thing, but not catching OperationCanceledException
s (probably from await
ed tasks) could set the task Canceled
although !cancellationToken.IsCancellationRequested
. So in order to have predictable and correct outcomes, do we need to wrap every cancellable async method into a try/catch making sure no OCE is thrown unless it's on the right CancellationToken
, or is there a more elegant way?
If the answer is no, then do we have to fall back to TaskCompletionSource
stuff?