0

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 Exception set) or
  • Canceled if the cancellationToken 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 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?

tinudu
  • 1,139
  • 1
  • 10
  • 20
  • In the opinion of the running code, it's been cancelled. That's why it threw an `OperationCanceledException`. If you disagree with this assessment, it's more of a people discussion between you and the developer of this code, not a technical problem to be fixed at call sites. – Damien_The_Unbeliever Jul 06 '18 at 12:30
  • That is to say, if you're worried about this scenario, why aren't you also worried about e.g. implementations that actually successfully complete their work and then throw some other exception, leading to the `Faulted` state rather then `RanToCompletion`, which is what they *should* have reported. – Damien_The_Unbeliever Jul 06 '18 at 12:42
  • Actually, I'm worried about 2 things: 1. If the implementing async method throws an OCE because the token is canceled (typically through `cancellationToken.ThrowIfCancellationRequested()`), is the returned task guaranteed to be in the `Canceled` state? Test code says yes, but seems to be an undocumented feature. 2. If some code that is called from within the async method throws an OCE on some other `CancellationToken`, the returned task ends up `Canceled` though the token passed to the method didn't request so. – tinudu Jul 10 '18 at 15:10

1 Answers1

0

From your example - it is not clear on the intent of your cancellationToken use. So I will have to speak from my usage of it. We use Windows services and need to know if the OnStop event has been used. TokenSource object has the Cancel method to signal to the Task that we need to stop operations. The Token boolean value indicates that cancellation is requested. By Throwing the OperationCancelled - you can catch that value and handle it gracefully. It is all in the name of Synchronization of controlling the task. Your scenario would have the same need in some way if you are doing this. Addtional Note 7/16/2018:

You call out in your question a signature ( with correct spelling on Task)

Task<int> ComputeAsync(..., CancellationToken cancellationToken)

But your example - builds an array of tasks that does not use that signature in any of the tasks. Your example is too incomplete to give an answer. (It seems you are not understanding signature of a task). When you invoke a task with a cancellation token your task action is where that elipses is in your signature example - and you are not showing what you are passing into the task.

You are using Task.FromResult which says - Return this result.

If you use Visual Studio F12 on the 'FromResult' method you will see the source code with this comment which states

Summary:
    //     Creates a System.Threading.Tasks.Task`1 that's completed 
successfully with the
    //     specified result.

It is a complete mismatch to your question. Here is a stackoverflow answer on what the FromResult is for What is the use for Task.FromResult<TResult> in C#

Mark W. Mitchell
  • 749
  • 1
  • 6
  • 8
  • Thank you for your answer. However, this does not answer my question, which is about how to implement the desired behavior as an `async` method, as opposed to returning a `TaskCompletionSource.Task`. – tinudu Jul 10 '18 at 14:56