How to handle exceptions with asynchronous in C#
By FoxLearn 1/3/2025 8:05:00 AM 329
While asynchronous programming allows for non-blocking execution of resource-intensive operations, handling errors in such methods requires a different approach.
This article explains the specific exception handling mechanisms for asynchronous methods in C#, highlighting how errors should be managed when working with async operations.
Exception handling in asynchronous vs synchronous
In synchronous C# code, exceptions are propagated up the call stack until they reach a suitable catch block. However, exception handling in asynchronous methods is more complex.
Asynchronous methods with a return type of Task
or Task<T>
wrap any exceptions in an AggregateException
and attach it to the Task object. If multiple exceptions occur, they are all stored within the Task.
For async methods with a void
return type, no Task object is involved. In this case, exceptions are raised on the active SynchronizationContext
at the time the async method was called.
Exceptions in asynchronous methods that return void
Consider a program where an asynchronous method returns void
and throws an exception:
public static void RunTest() { try { PerformAsyncOperation(); } catch (Exception ex) { Console.WriteLine($"Error caught: {ex.Message}"); } } private static async void PerformAsyncOperation() { throw new Exception("Something went wrong during the async operation."); }
When you run the RunTest()
method, you’ll notice that the exception is not caught by the catch
block in RunTest()
. This is because PerformAsyncOperation()
returns void
and exceptions thrown inside it are not automatically propagated to the calling method’s exception handling mechanism.
Exceptions in asynchronous methods that return a Task object
Consider a scenario where multiple exceptions are thrown in asynchronous tasks that return Task
objects. The exceptions are wrapped in an AggregateException
and returned to the calling method. When awaiting the task, only the first exception is exposed from the list of exceptions.
public static async Task HandleMultipleExceptionsAsync() { try { var task1 = Task.Run(() => throw new NullReferenceException("NullReferenceException occurred.")); var task2 = Task.Run(() => throw new InvalidOperationException("InvalidOperationException occurred.")); await Task.WhenAll(task1, task2); } catch (AggregateException ex) { Console.WriteLine($"Caught AggregateException: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Caught Exception: {ex.Message}"); } }
When you run the HandleMultipleExceptionsAsync()
method, you will notice that the message "NullReferenceException occurred." will be displayed in the console. Even though both task1
and task2
throw exceptions, only the first exception (NullReferenceException
) is caught and displayed. The exceptions are wrapped inside an AggregateException
, but only the first one is handled by the catch (Exception)
block after the await
.
How to use AggregateException.Handle to handle exceptions in C#
Consider the following program where multiple exceptions are thrown, and we handle only the specific ones we are interested in:
public async static Task HandleSelectedExceptionsAsync() { try { var task1 = Task.Run(() => throw new ArgumentNullException("ArgumentNullException is thrown.")); var task2 = Task.Run(() => throw new FileNotFoundException("FileNotFoundException is thrown.")); Task.WaitAll(task1, task2); } catch (AggregateException ae) { ae.Handle(ex => { if (ex is ArgumentNullException) { Console.WriteLine($"Handled: {ex.Message}"); return true; // Handled successfully } // Ignore FileNotFoundException if (ex is FileNotFoundException) { Console.WriteLine($"Ignored: {ex.Message}"); return false; // Not handled, continue } return false; // Not handled }); } }
In this example, the ArgumentNullException
is handled and printed to the console, while the FileNotFoundException
is ignored. The Handle
method of AggregateException
is used to specify which exceptions to process and which ones to ignore.
Finally, you can invoke this method from your Main
function like this:
static async Task Main(string[] args) { await HandleSelectedExceptionsAsync(); Console.Read(); }
This example demonstrates how you can leverage asynchronous programming to make applications scalable and responsive. When working with async methods, remember that the exception handling logic differs from that of synchronous code, allowing you to manage specific errors selectively.
- Using the OrderBy and OrderByDescending in LINQ
- Querying with LINQ
- Optimizing Performance with Compiled Queries in LINQ
- MinBy() and MaxBy() Extension Methods in .NET
- SortBy, FilterBy, and CombineBy in NET 9
- Exploring Hybrid Caching in .NET 9.0
- Using Entity Framework with IDbContext in .NET 9.0
- Primitive types in C#