Course contentsShow
← C#
Lesson 574 of 1,9086. Asynchronous Programming and ConcurrencyFree lesson

Handling Asynchronous Exceptions

Implement robust try-catch blocks that correctly capture and aggregate exceptions thrown within asynchronous tasks.

In this lesson, you will learn how to wrap your asynchronous operations in robust try-catch blocks to ensure exceptions don't vanish into the background.

Why Async Exceptions Are Tricky

When you run a synchronous method, if an error occurs, it bubbles up the call stack immediately. However, when you use async/await, your method is transformed into a state machine. If an exception occurs inside a Task, it is captured by the task object itself and held until that task is "awaited." If you don't await the task or handle the result correctly, the exception might remain "swallowed" or unobserved.

Implementing Robust Error Handling

To catch these errors, you must surround your await expressions with standard try-catch blocks. The await keyword acts as the trigger; when the task finishes with an error, the exception is "unwrapped" and re-thrown in the calling context.

Consider this example of a file-reading operation:

public async Task ProcessDataAsync(string filePath)
{
    try
    {
        // The exception is caught here when the task is awaited
        string content = await File.ReadAllTextAsync(filePath);
        Console.WriteLine(content);
    }
    catch (FileNotFoundException ex)
    {
        Console.WriteLine($"File missing: {ex.Message}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"An unexpected error occurred: {ex.Message}");
    }
}

Dealing with Multiple Tasks

If you are running multiple tasks concurrently (for instance, using Task.WhenAll), the await expression will only re-throw the first exception that occurred. If you need to inspect all of them, you can capture the returned Task object.

Task task1 = DoWorkA();
Task task2 = DoWorkB();

try
{
    await Task.WhenAll(task1, task2);
}
catch
{
    // Check the Exception property of the original tasks
    if (task1.IsFaulted) Console.WriteLine(task1.Exception.InnerException);
    if (task2.IsFaulted) Console.WriteLine(task2.Exception.InnerException);
}

By accessing the Exception property on the individual tasks, you gain access to the AggregateException, which contains the full list of errors that occurred across your parallel operations.

Key Takeaway: Always await your tasks within a try-catch block to ensure exceptions are properly re-thrown and handled, or inspect the Exception property if you are managing multiple tasks via Task.WhenAll.