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?
source to share
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.
source to share
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.
source to share
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.
}
source to share