Exception handling is different for asynchronous code. Learn the exception handling semantics for asynchronous methods in C#. Credit: graemenicholson / Getty Images Exception handling is the technique of handling runtime errors in an application. Asynchronous programming allows us to perform resource-intensive operations without the need for blocking on the main or executing thread of the application. However, note that the error handling mechanism of an asynchronous method is different from that of a synchronous method. This article presents a discussion on how we can handle exceptions when working with asynchronous code in C#. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Create a .NET Core console application project in Visual Studio 2019 First off, let’s create a .NET Core Console Application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core Console Application project in Visual Studio 2019. Launch the Visual Studio IDE. Click “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window shown next, specify the name and location for the new project. Click Create. This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project in the subsequent sections of this article. Exception handling in asynchronous vs. synchronous code In synchronous C# code, the exceptions are propagated up the call stack until they reach an appropriate catch block that can handle the exception. However, exception handling in asynchronous methods is not as straightforward. An asynchronous method in C# can have three types of return value: void, Task, and Task. When an exception occurs in an async method that has a return type of Task or Task, the exception object is wrapped in an instance of AggregateException and attached to the Task object. If multiple exceptions are thrown, all of them are stored in the Task object. However, asynchronous methods that have a return type of void don’t have a Task object associated with them. If exceptions are thrown in such methods, the exceptions are raised on the SynchronizationContext that was active at the time the asynchronous method was called. [ Also on InfoWorld: .NET Framework APIs that won’t be coming to .NET 5.0 ] Exceptions in asynchronous methods that return void The following program shows an asynchronous method that returns void and throws an exception. public static void ThisIsATestMethod() { try { AsyncMethodReturningVoid(); } catch (Exception ex) { Console.WriteLine(ex.Message); } } private static async void AsyncMethodReturningVoid() { throw new Exception("This is an error message..."); } When you execute the ThisIsATestMethod() method above, you’ll observe that the catch block of the calling method, i.e., ThisIsATestMethod, is not called. Exceptions in asynchronous methods that return a Task object When an exception is thrown in an asynchronous method that returns a Task object, the exceptions are wrapped in an instance of AggregateException and returned to the calling method. When we await the task, we get only the first exception from the list of exceptions that may have occurred. The following program illustrates this. public static async Task ExceptionInAsyncCodeDemo() { try { var task1 = Task.Run(() => throw new IndexOutOfRangeException ("IndexOutOfRangeException is thrown.")); var task2 = Task.Run(() => throw new ArithmeticException ("ArithmeticException is thrown.")); await Task.WhenAll(task1, task2); } catch (AggregateException ex) { Console.WriteLine(ex.Message); } catch (Exception ex) { Console.WriteLine(ex.Message); } } When you execute the above method, the message “IndexOutOfRangeException is thrown” will be displayed in the console window. Once the exception is thrown, the control will enter the second catch block, i.e., the catch block that uses Exception as its type parameter. Hence, although two exceptions have been thrown, we’ll receive only one of them. Use the Exceptions property to retrieve all exceptions To retrieve all of the exceptions that have been thrown, we can take advantage of the Exceptions property of the Task instance. The following code listing shows how we can retrieve all exceptions that have occurred in a method that returns a Task instance. public static async Task ExceptionInAsyncCodeDemo() { Task tasks = null; try { var task1 = Task.Run(() => throw new IndexOutOfRangeException ("IndexOutOfRangeException is thrown.")); var task2 = Task.Run(() => throw new ArithmeticException("ArithmeticException is thrown.")); tasks = Task.WhenAll(task1, task2); await tasks; } catch { AggregateException aggregateException = tasks.Exception; foreach (var e in aggregateException.InnerExceptions) { Console.WriteLine(e.GetType().ToString()); } } } Use AggregateException.Handle to handle exceptions You can take advantage of AggregateException.Handle to handle certain exceptions while at the same time ignoring those exceptions that you don’t want to handle. The following code snippet shows how this can be achieved. public async static Task ExceptionInAsyncCodeDemo() { try { var task1 = Task.Run(() => throw new IndexOutOfRangeException ("IndexOutOfRangeException is thrown.")); var task2 = Task.Run(() => throw new ArithmeticException ("ArithmeticException is thrown.")); Task.WaitAll(task1, task2); } catch (AggregateException ae) { ae.Handle(ex => { if (ex is IndexOutOfRangeException) Console.WriteLine(ex.Message); return ex is InvalidOperationException; }); } } In the code example given above, the IndexOutOfRangeException is handled while the InvalidOperationException is ignored. Finally, here is how you can call the ExceptionInAsyncCodeDemo() method from the Main method of the Program.cs file. static async Task Main(string[] args) { await ExceptionInAsyncCodeDemo(); Console.Read(); } You can take advantage of asynchronous programming to build applications that are scalable and responsive. However, when you use asynchronous methods, keep in mind that the error handling semantics of those methods are different from those of a synchronous method. Related content how-to How to use FastEndpoints in ASP.NET Core Take advantage of the free open-source FastEndpoints library to build fast and lean APIs in your ASP.NET Core applications. By Joydip Kanjilal Jul 11, 2024 7 mins Microsoft .NET C# Development Libraries and Frameworks how-to How to use Refit to consume APIs in ASP.NET Core Take advantage of Refit REST library to simplify API consumption and make your code cleaner, more efficient, and easier to maintain. By Joydip Kanjilal Jul 04, 2024 10 mins C# Microsoft .NET Software Deployment how-to When to use an abstract class vs. interface in C# Understanding the differences between an abstract class and interface is key to designing loosely coupled and extensible applications. By Joydip Kanjilal Jun 20, 2024 10 mins Small and Medium Business Microsoft .NET C# how-to 6 security best practices for ASP.NET Core Learn the best practices and built-in safeguards for preventing attacks and protecting sensitive data in your ASP.NET Core web applications. By Joydip Kanjilal Jun 07, 2024 6 mins C# Microsoft .NET Web Development Resources Videos