Problem
Several tasks are run in parallel, and all, none, or any of them might throw exceptions. When all the tasks have finalized, all the exceptions that might have happened must be reported (via log, email, console output.... whatever).
Expected behavior
I can build all the tasks via linq with async lambdas, and then await for them running in parallel with Task.WhenAll(tasks)
. Then I can catch an AggregateException
and report each of the individual inner exceptions.
Actual behavior
An AggregateException
is thrown, but it contains just one inner exception, whatever number of individual exceptions have been thrown.
Minimal complete verifiable example
static void Main(string[] args)
{
try
{
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex)
{
ex.Handle(innerEx =>
{
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Console.ReadLine();
}
private static async Task ThrowSeveralExceptionsAsync(int nExceptions)
{
var tasks = Enumerable.Range(0, nExceptions)
.Select(async n =>
{
await ThrowAsync(new Exception($"Exception #{n}"));
});
await Task.WhenAll(tasks);
}
private static async Task ThrowAsync(Exception ex)
{
await Task.Run(() => {
Console.WriteLine($"I am going to throw \"{ex.Message}\"");
throw ex;
});
}
Output
Note that the output order of the "I am going to throw" messages might change, due to race conditions.
I am going to throw "Exception #0"
I am going to throw "Exception #1"
I am going to throw "Exception #2"
I am going to throw "Exception #3"
I am going to throw "Exception #4"
"Exception #0" was thrown
That's because await
"unwraps" aggregate exceptions and always throws just first exception (as described in documentation of await), even when you await Task.WhenAll
which obviously can result in multiple errors. You can access aggregate exception for example like this:
var whenAll = Task.WhenAll(tasks);
try {
await whenAll;
}
catch {
// this is `AggregateException`
throw whenAll.Exception;
}
Or you can just loop over tasks and check status and exception of each.
Note that after that fix you need to do one more thing:
try {
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex) {
// flatten, unwrapping all inner aggregate exceptions
ex.Flatten().Handle(innerEx => {
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Because task returned by ThrowSeveralExceptionsAsync
contains AggregateException
we thrown, wrapped inside another AggregateException
.
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