When causing multiple exceptions in a Task.WhenAll
call, it looks like only one of the exceptions is absorbed into the Task once you await it through more than one layer of awaiting. I was under the impression that the Task.Exception.InnerExceptions
property would contain all exceptions that occurred, but under certain circumstances they seem to only have one.
For example, this sample code creates multiple exception-throwing Tasks and then awaits a Task.WhenAll on them, and then writes to console the exceptions that it is able to catch:
class Program
{
static async Task Main(string[] args)
{
var task = CauseMultipleExceptionsAsync();
// Delaying until all the Exceptions have been thrown, ensuring it isn't just a weird race condition happening behind the scenes
await Task.Delay(5000);
try
{
await task;
}
catch(AggregateException e)
{
// This does not get hit
Console.WriteLine($"AggregateException caught: Found {e.InnerExceptions.Count} inner exception(s)");
}
catch(Exception e)
{
Console.WriteLine($"Caught other Exception {e.Message}");
Console.WriteLine($"task.Exception.InnerExceptions contains {task.Exception.InnerExceptions.Count} exception(s)");
foreach (var exception in task.Exception.InnerExceptions)
{
Console.WriteLine($"Inner exception {exception.GetType()}, message: {exception.Message}");
}
}
}
static async Task CauseMultipleExceptionsAsync()
{
var tasks = new List<Task>()
{
CauseExceptionAsync("A"),
CauseExceptionAsync("B"),
CauseExceptionAsync("C"),
};
await Task.WhenAll(tasks);
}
static async Task CauseExceptionAsync(string message)
{
await Task.Delay(1000);
Console.WriteLine($"Throwing exception {message}");
throw new Exception(message);
}
}
I was expecting this to either enter the catch(AggregateException e)
clause, or at least to have three inner exceptions in task.Exception.InnerExceptions
- what actually happens that one one exception is raised, and only only one of the exceptions is in task.Exception.InnerExceptions
:
Throwing exception B
Throwing exception A
Throwing exception C
Caught other Exception A
task.Exception.InnerExceptions contains 1 exception(s)
Inner exception System.Exception, message: A
What is weirder is that this behaviour changes depending on whether you await the Task.WhenAll
call in CauseMultipleExceptionsAsync
- if you return the Task directly rather than awaiting it, then all three exceptions appear in task.Exception.InnerException
. For instance, replacing CauseMultipleExceptionsAsync
with this:
static Task CauseMultipleExceptionsAsync()
{
var tasks = new List<Task>()
{
CauseExceptionAsync("A"),
CauseExceptionAsync("B"),
CauseExceptionAsync("C"),
};
return Task.WhenAll(tasks);
}
Gives this result, with all three exceptions contained in task.Exception.InnerExceptions:
Throwing exception C
Throwing exception A
Throwing exception B
Caught other Exception A
task.Exception.InnerExceptions contains 3 exception(s)
Inner exception System.Exception, message: A
Inner exception System.Exception, message: B
Inner exception System.Exception, message: C
I'm quite confused about this - where did exceptions B and C go in the initial example? How would you go about finding them again if Task.Exception doesn't contain any information about them? Why does awaiting inside CauseMultipleExceptionsAsync
hide these exceptions, while returning the Task.WhenAll
directly does not?
If it makes a difference, I am able to replicate the above in both .Net Framework 4.5.2 and .Net Core 2.1.