The description of the Task.WhenAny
method says, that it will return the first task finished, even if it's faulted. Is there a way to change this behavior, so it would return first successful task?

- 369
- 1
- 5
- 13
-
What's the faulted mean? – D-Shih Mar 16 '18 at 05:11
-
Nope, just do it yourself (call it in the loop until it returns successful task). – Evk Mar 16 '18 at 05:27
-
I would guess that’s possible by using `taskLikelyToFault.ContinueWith(t => t.Exception, TaskContinuationOptions.OnlyOnFaulted)` to automatically trigger a continuation task in case your actual task faults (and also observe the exception to avoid it being rethrown by the synchronisation context as an unobserved exception) and using this one as argument to WhenAny. – ckuri Mar 16 '18 at 05:36
-
1Related: [How to implement Task.WhenAny() with a predicate](https://stackoverflow.com/questions/38289158/how-to-implement-task-whenany-with-a-predicate) – Theodor Zoulias May 25 '20 at 05:35
-
Another related question: [Is there default way to get first task that finished successfully?](https://stackoverflow.com/questions/37528738/is-there-default-way-to-get-first-task-that-finished-successfully) – Theodor Zoulias Aug 20 '23 at 19:23
2 Answers
Something like this should do it (may need some tweaks - haven't tested):
private static async Task<Task> WaitForAnyNonFaultedTaskAsync(IEnumerable<Task> tasks)
{
IList<Task> customTasks = tasks.ToList();
Task completedTask;
do
{
completedTask = await Task.WhenAny(customTasks);
customTasks.Remove(completedTask);
} while (completedTask.IsFaulted && customTasks.Count > 0);
return completedTask.IsFaulted?null:completedTask;
}

- 2,819
- 20
- 31
-
I am just asking but doesn't this sequentially wait for the tasks to execute and complete then check if it completed successfully, other than running them all in parallel then finding the first that completed? – Nico Mar 16 '18 at 06:01
-
Kind of. The iteration will break as soon a non-faulted task will complete or all tasks will turn out to be faulted. Technically I should have updated it to return null if all are faulted - can be corrected really. If all the tasks will be faulted - the code will indeed iterate over all those tasks. – Artak Mar 16 '18 at 06:38
-
-
Understood, its still a synchronous execution using the WhenAny. I have reviewed the code for how the WhenAny works and don't actually think there is a "good" solution to this without rewriting that which is internal to the task framework. My example tries to do it in parallel but even I wouldn't use it in production code. – Nico Mar 16 '18 at 06:43
-
According to documentation "The return task's Result is the task that completed." and "The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.". So your condition is wrong. You'd need to check for the Status of completedTask's Result not completedTask's status itself. – alfoks Jan 07 '21 at 12:41
-
Task could be Cancelled (but not Faulted) as well. How about checking for `while (!completedTask.IsCompletedSuccessfully && customTasks.Count > 0);`. Of course, need to reverse the logic for return as well. – Saar Dec 29 '21 at 20:18
First off, from my review there is no direct way of doing this without waiting for all the tasks to complete then find the first one that ran successfully.
To start with I am not sure of the edge cases that will cause issues that I havent tested, and given the source code around tasks and contiunuation requires more than an hour of review I would like to start to think around the follow source code. Please review my thoughts at the bottom.
public static class TaskExtensions
{
public static async Task<Task> WhenFirst(params Task[] tasks)
{
if (tasks == null)
{
throw new ArgumentNullException(nameof(tasks), "Must be supplied");
}
else if (tasks.Length == 0)
{
throw new ArgumentException("Must supply at least one task", nameof(tasks));
}
int finishedTaskIndex = -1;
for (int i = 0, j = tasks.Length; i < j; i++)
{
var task = tasks[i];
if (task == null)
throw new ArgumentException($"Task at index {i} is null.", nameof(tasks));
if (finishedTaskIndex == -1 && task.IsCompleted && task.Status == TaskStatus.RanToCompletion)
{
finishedTaskIndex = i;
}
}
if (finishedTaskIndex == -1)
{
var promise = new TaskAwaitPromise(tasks.ToList());
for (int i = 0, j = tasks.Length; i < j; i++)
{
if (finishedTaskIndex == -1)
{
var taskId = i;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
//we dont want to await these tasks as we want to signal the first awaited task completed.
tasks[i].ContinueWith((t) =>
{
if (t.Status == TaskStatus.RanToCompletion)
{
if (finishedTaskIndex == -1)
{
finishedTaskIndex = taskId;
promise.InvokeCompleted(taskId);
}
}
else
promise.InvokeFailed();
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
return await promise.WaitCompleted();
}
return Task.FromResult(finishedTaskIndex > -1 ? tasks[finishedTaskIndex] : null);
}
class TaskAwaitPromise
{
IList<Task> _tasks;
int _taskId = -1;
int _taskCount = 0;
int _failedCount = 0;
public TaskAwaitPromise(IList<Task> tasks)
{
_tasks = tasks;
_taskCount = tasks.Count;
GC.KeepAlive(_tasks);
}
public void InvokeFailed()
{
_failedCount++;
}
public void InvokeCompleted(int taskId)
{
if (_taskId < 0)
{
_taskId = taskId;
}
}
public async Task<Task> WaitCompleted()
{
await Task.Delay(0);
while (_taskId < 0 && _taskCount != _failedCount)
{
}
return _taskId > 0 ? _tasks[_taskId] : null;
}
}
}
The code is lengthy I understand and may have lots of issues, however the concept is you need to execute all the tasks in parallel and find the first resulting task that completed successfully.
If we consider that we need to make a continuation block of all the tasks and be able to return out of the continuation block back to the original caller. My main concern (other than the fact I cant remove the continuation) is the while()
loop in the code. Probably best to add some sort of CancellationToken and/or Timeout to ensure we dont deadlock while waiting for a completed task. In this case if zero tasks complete we never finish this block.
Edit I did change the code slightly to signal the promise for a failure so we can handle a failed task. Still not happy with the code but its a start.

- 12,493
- 5
- 42
- 62
-
2"there is direct way of doing this without waiting for all the tasks to complete " - why. `Task.WhenAny` doesn't wait for all tasks to complete, it returns as soon as any completes. All other (incompleted yet) tasks continue running. So code from other answer is fine, there is no need in such complications like in your answer. – Evk Mar 16 '18 at 07:37