Handling AggregateException in TPL
When working with the Task Parallel Library (TPL) in C#, you are often running multiple operations simultaneously. While this improves performance, it creates a unique challenge: What happens if multiple tasks fail at the same time?
Standard exception handling usually catches only one error. However, TPL wraps all exceptions from parallel tasks into a single wrapper called AggregateException.
Here are the best practices for handling these complex error scenarios effectively.
1. The Challenge with await and AggregateException
A common misconception is that a simple try-catch block around an await call will catch an AggregateException. In reality, the await keyword unwraps the exception and throws only the first one it finds, discarding the others.
To correctly capture the AggregateException (and thus see all failures), you should assign the task to a variable first, await it safely, and then inspect the Task.Exception property.
2. Flattening the Hierarchy
AggregateException can be nested. If you have a task that starts another child task, and both fail, you might end up with an AggregateException inside another AggregateException.
Parsing this tree manually is tedious. The .Flatten() method recursively unwrap all nested aggregate exceptions into a single, flat list of errors.
3. Handling Specific Exception Types
When multiple things go wrong, you often need to react differently to different errors. You might want to retry on a TimeoutException but log and abort on a AuthorizationException.
You can iterate through the inner exceptions and use pattern matching (or the is keyword) to handle them individually.
4. Handling Exceptions "As They Occur" (ContinueWith)
Sometimes you don't want to wait for all tasks to finish (or fail) before you start logging errors. You can attach a continuation using .ContinueWith to handle exceptions the moment a specific task fails.
This approach keeps your main thread free from complex try-catch blocks and delegates error handling to the task itself.
Summary
| Method | Best Use Case |
Inspect Task.Exception | When you need to see every error that happened during Task.WhenAll. |
Flatten() | When you have complex, nested tasks and just want a simple list of errors. |
ContinueWith | When you want to log or handle errors immediately without waiting for the whole batch to finish. |