C# > Asynchronous Programming > Tasks and async/await > Exception Handling in Async Code

Exception Handling with Task.WhenAll and AggregateException

This snippet demonstrates how to handle exceptions when using Task.WhenAll. Task.WhenAll aggregates exceptions into an AggregateException, which needs to be handled appropriately.

Implementation

The SimulateAsyncOperation method simulates an asynchronous operation that may or may not throw an exception. The Main method creates a list of tasks and uses Task.WhenAll to wait for all of them to complete. If any of the tasks throw an exception, Task.WhenAll will throw an AggregateException containing all the inner exceptions. The code then iterates through the InnerExceptions property of the AggregateException to handle each individual exception.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class TaskWhenAllExceptionHandler
{
    public static async Task SimulateAsyncOperation(int id, bool throwException)
    {
        Console.WriteLine($"Task {id}: Started");
        await Task.Delay(1000); // Simulate some work

        if (throwException)
        {
            throw new InvalidOperationException($"Task {id}: Simulated exception.");
        }

        Console.WriteLine($"Task {id}: Completed successfully.");
    }

    public static async Task Main(string[] args)
    {
        List<Task> tasks = new List<Task>
        {
            SimulateAsyncOperation(1, false),
            SimulateAsyncOperation(2, true),
            SimulateAsyncOperation(3, false),
            SimulateAsyncOperation(4, true)
        };

        try
        {
            await Task.WhenAll(tasks);
            Console.WriteLine("All tasks completed successfully.");
        }
        catch (AggregateException ex)
        {
            Console.WriteLine("AggregateException caught:");
            foreach (var innerException in ex.InnerExceptions)
            {
                Console.WriteLine($"  {innerException.GetType().Name}: {innerException.Message}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception caught: {ex.Message}");
        }
    }
}

Concepts Behind the Snippet

Task.WhenAll is used to wait for multiple tasks to complete. When one or more of these tasks throw an exception, these exceptions are aggregated into an AggregateException. This design pattern allows the application to process multiple asynchronous operations concurrently and handle any errors that may occur in a consolidated manner. Proper handling of AggregateException is crucial for managing failures within asynchronous workflows.

Real-Life Use Case

This pattern is useful when performing multiple independent operations simultaneously, such as downloading multiple files, querying multiple databases, or processing multiple API requests. If any of these operations fail, the application can collect all the errors and present a comprehensive error report to the user or retry the failed operations.

Best Practices

  • Always handle AggregateException when using Task.WhenAll or Task.WhenAny.
  • Iterate through the InnerExceptions property of the AggregateException to handle each individual exception.
  • Consider using a logging framework to record the details of each exception.
  • Implement a retry mechanism for failed tasks, if appropriate.

Interview Tip

Be prepared to explain how Task.WhenAll handles exceptions and why it's important to handle the AggregateException. Discuss the benefits of using Task.WhenAll for parallel processing and the challenges of handling exceptions in a concurrent environment.

When to Use Them

Use Task.WhenAll when you need to wait for multiple independent tasks to complete and you want to handle any exceptions that may occur in a consolidated manner. This is particularly useful for performance-critical applications where parallel processing is essential.

Alternatives

Instead of Task.WhenAll, you can use Task.WhenAny to wait for the first task to complete. Another alternative is to use a Parallel.ForEach loop to process multiple items concurrently. However, Parallel.ForEach does not provide as robust exception handling as Task.WhenAll.

Pros

  • Allows for parallel processing, improving performance.
  • Aggregates exceptions into a single AggregateException, making it easier to handle multiple errors.
  • Provides a clean and concise way to wait for multiple tasks to complete.

Cons

  • Requires careful handling of the AggregateException to process individual exceptions.
  • Can be more complex to implement than sequential processing.

FAQ

  • What is an AggregateException?

    An AggregateException is an exception that contains a collection of other exceptions. It is typically thrown by methods that perform multiple asynchronous operations in parallel and need to report multiple errors.
  • How do I access the individual exceptions within an AggregateException?

    You can access the individual exceptions by iterating through the InnerExceptions property of the AggregateException. Each element in the InnerExceptions collection represents an individual exception that occurred during the asynchronous operations.