Given a method signature like
Taks<int> ComputeAsync(..., CancellationToken cancellationToken)
, one would expect the returned task to complete as
- RanToCompletion(and the- Resultset) or
- Faulted(and- Exceptionset) or
- Canceledif the- cancellationTokenrequested 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 OperationCanceledExceptions (probably from awaited 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?
 
    