Joydip Kanjilal
Contributor

How to handle exceptions in asynchronous code in C#

how-to
Nov 18, 20195 mins
C#Microsoft .NETSoftware Development

Exception handling is different for asynchronous code. Learn the exception handling semantics for asynchronous methods in C#.

women spinning plates asynchronous programming synchrony multi tasking by graemenicholson getty ima
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.

  1. Launch the Visual Studio IDE.
  2. Click “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. 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.

Joydip Kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author