Given the following:
var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));
var task = Task.WhenAll(tPass1, tFail1, tFail2);
task.Wait();
the call to task.Wait() throws an AggregateException
, whose inner exceptions contain the fail1
and fail2
exceptions. But how can I access the tPass1
successful result?
Is this possible?
I'm aware that I can get the result from the individual task after the WhenAll
has finished, via tPass1.Result
however is there a way to get them in an array to avoid having to manually track all the things feeding into the WhenAll
?
Maybe
public async Task<Task[]> RejectFailedFrom(params Task[] tasks)
{
try
{
await Task.WhenAll(tasks);
}
catch(Exception exception)
{
// Handle failed tasks maybe
}
return tasks.Where(task => task.Status == TaskStatus.RanToCompletion).ToArray();
}
Usage
var tasks = new[]
{
Task.FromResult(1),
Task.FromException<int>(new ArgumentException("fail1")),
Task.FromException<int>(new ArgumentException("fail2"))
};
var succeed = await RejectFailedFrom(tasks);
// [ tasks[0] ]
When a task fails we cannot access its Result
property because it throws. So to have the results of a partially successful WhenAll
task, we must ensure that the task will complete successfully. The problem then becomes what to do with the exceptions of the failed internal tasks. Swallowing them is probably not a good idea. At least we would like to log them. Here is an implementation of an alternative WhenAll
that never throws, but returns both the results and the exceptions in a ValueTuple
struct.
public static Task<(T[] Results, Exception[] Exceptions)> WhenAllEx<T>(
params Task<T>[] tasks)
{
ArgumentNullException.ThrowIfNull(tasks);
tasks = tasks.ToArray(); // Defensive copy
return Task.WhenAll(tasks).ContinueWith(t => // return a continuation of WhenAll
{
T[] results = tasks
.Where(t => t.IsCompletedSuccessfully)
.Select(t => t.Result)
.ToArray();
AggregateException[] aggregateExceptions = tasks
.Where(t => t.IsFaulted)
.Select(t => t.Exception) // The Exception is of type AggregateException
.ToArray();
Exception[] exceptions = new AggregateException(aggregateExceptions).Flatten()
.InnerExceptions.ToArray(); // Flatten the hierarchy of AggregateExceptions
if (exceptions.Length == 0 && t.IsCanceled)
{
// No exceptions and at least one task was canceled
exceptions = new[] { new TaskCanceledException(t) };
}
return (results, exceptions);
}, default, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
Usage example:
var tPass1 = Task.FromResult(1);
var tFail1 = Task.FromException<int>(new ArgumentException("fail1"));
var tFail2 = Task.FromException<int>(new ArgumentException("fail2"));
var task = WhenAllEx(tPass1, tFail1, tFail2);
task.Wait();
Console.WriteLine($"Status: {task.Status}");
Console.WriteLine($"Results: {String.Join(", ", task.Result.Results)}");
Console.WriteLine($"Exceptions: {String.Join(", ", task.Result.Exceptions.Select(ex => ex.Message))}");
Output:
Status: RanToCompletion
Results: 1
Exceptions: fail1, fail2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With