Task.WhenAny - task canceled

In the following code:

        if (await Task.WhenAny(task, Task.Delay(100)) == task) {
            success = true;
        }
        else {
            details += "Timed out on SendGrid.";
            await task.ContinueWith(s =>
            {
                LogError(s.Exception);
            }, TaskContinuationOptions.OnlyOnFaulted);
        }

      

Sometimes I get A task was cancelled

on call await task.ContinueWith

. My goal here is to check if completes task

within 100ms - if it doesn't, I want to handle some logging (this particular task has a resource leak, so I'm trying to work around it by wrapping it with a timeout). This is done here: Debugging Task.WhenAny and Push Notifications

Why is this happening, and what can I do to rule out this exception?

+3


source to share


3 answers


This happens when yours was task

unable to complete in time (in 100ms) but was later able to complete it. You execute your continuation with help TaskContinuationOptions.OnlyOnFaulted

and such tasks were canceled unless the original task was corrupted. You are the await

result ContinueWith

, so if your task is not a failure, your continuation is canceled and you have an exception.

In general, this way of handling a timeout doesn't make a lot of sense, because even after the timeout has been reached, you still wait for the original task to complete in order to log an exception. I think you need to uninstall await

before continuing. The code will then continue to time out, but if the task fails later, it will be written.

There is another problem in your code - it Task.WhenAny

will never throw. So this is the condition:

await Task.WhenAny(task, Task.Delay(100)) == task

      

Does NOT mean success as it task

can be damaged. Always check task.Status

and task.Exception

even if WhenAny

indicates the completion of your task. By the way, this answer you linked in your question mentions this.



Update: if you don't like VS warning about not expected call - you can turn it off for that particular line, or use an extension method like this:

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        // do nothing
    }
}

      

And then:

task.ContinueWith(s => {
    Logger.Write(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted).Forget();

      

There is no harm in doing this (in this particular case, of course), VS just issues this warning for every potentially awaited call that is not expected.

+4


source


Tasks are canceled if they do not start. The OnlyOnFaulted function will execute as you continue your task. If it is not an error, then the continuation will be canceled.

Your two options, which are unrelated to handling this particular exception, are either not waiting for something (and don't know if this work has finished or not), or waiting for the original task. Once the wait is over, the continuation of the task will either be started or canceled at that point.



If you are good at dealing with cancellation, just catch the TaskCancelledException (or AggregateException if needed) while the task continues and discard the exception.

The best way, in my opinion, for this particular case (registering the exception) is to simply wait for the original task again and handle the TaskCancelledException, without the need to continue the task, and is fully asynchronous / awaiting.

0


source


As @Evk mentioned yours Continuation

is being canceled. But I want to add that the continuation can be considered as part of the configuration and as such set at build time Task

. Consider the following:

var task = Task.Delay(500).ContinueWith(s =>
{
    LogError(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);

if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
} else {
    details += "Timed out on SendGrid.";
}

      

In this approach, your exception is still being logged and your logic will continue just knowing that the timeout is Task

. If the rest of the logic requires knowledge Exception

in addition to the timeout, then you will need to await

Task

again, as already discussed.

Update for clarity

Source Code (1) At this point, we cannot determine where Task

.

//task is undeclared in this snippet
if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
}
else {
    details += "Timed out on SendGrid.";
    await task.ContinueWith(s =>
    {
        LogError(s.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}

      

(2) . Add the added layout task, for example

var task = Task.Delay(500); //defines task as a Task that will complete in 500ms
if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
}
else {
    details += "Timed out on SendGrid.";
    await task.ContinueWith(s =>
    {
        LogError(s.Exception);
    }, TaskContinuationOptions.OnlyOnFaulted);
}

      

(3) Next, when we are await task.ContinueWith

, we allow to throw a TaskCancelledException

. If await

deleted, Exception

can be ignored, but we get a warning for a task that is not expected. We can ignore the warning, or we can admit that Continuation

we can consider it as part of a specific configuration Task

. With this we can customize Continuation

where we create the task with the appropriate parameters, that is TaskContinuationOptions.OnlyOnFaulted

:

//Add continuation configuration where task is created
var task = Task.Delay(500).ContinueWith(s =>
{
    LogError(s.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);

if (await Task.WhenAny(task, Task.Delay(100)) == task) {
    success = true;
} else {
    details += "Timed out on SendGrid.";
    //removed continuation from here.
}

      

0


source







All Articles