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 feature What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos