Handling Exceptions in Asynchronous Methods in C#

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

C# Programming Guide

The asynchronous programming model in C# allows developers to write code that is scalable, responsive, and reliable by using the power of your CPU cores. One thing to understand, however, is that there are limitations on how programmers can handle exceptions in an asynchronous method.

This C# programming tutorial discusses exception handling, how it differs in synchronous and asynchronous methods, and how you can handle exceptions elegantly and efficiently in asynchronous methods.

Read: Best Online Courses to Learn C#

What are Exceptions and Exception Handling in C#?

Exceptions are errors that occur at the time when your application is in execution. If exceptions are not handled properly in your code, the application’s main thread will terminate when the runtime throws an exception. Exception handling is defined as the process of handling exceptions in your application’s code.

What is Asynchronous Programming in C#?

Asynchronous programming enables developers to perform resource-intensive operations without halting the application’s main or execution thread. Asynchronous programming has several benefits – for starters, it helps improve the responsiveness and scalability of your application remarkably. However, it should be noted that exception propagation in synchronous and asynchronous code works differently.

Asynchronous programming enables you to build applications that do not block each statement or command, allowing the application to continue executing other instructions. As a consequence, asynchronous programming will enable you to develop apps that are more scalable and responsive.

The method signature of an asynchronous method includes the async keyword and one or more await statements. Note that await is a unary operator, with the operand being the name of the method to be awaited.

How Does Exception Handling in Synchronous and Asynchronous Methods Differ?

Code that throws an exception in a synchronous method propagates up the call stack until the call stack finds a catch block that can handle it. However, managing exceptions in asynchronous methods is more complicated. In C#, an asynchronous method may return one of three values: void, Task, or TaskTResult>. Exceptions thrown in async methods with Task or TaskTResult> return types are wrapped in an AggregateException instance and associated with the Task instance. If more than one exception is thrown, they are all stored in the Task object. Asynchronous methods having a return type of void, on the other hand, do not have a Task object associated with them.

Handling Exceptions in Asynchronous Methods that Return Void

Now, refer to the following C# code example that throws an exception inside an async method:

public class AsyncTest
    {
        public static void Test()
        {
            try
            {
                ThrowErrorAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        private static async void ThrowExceptionAsync()
        {
            throw new Exception("An error occurred...");
        }
    }

When you execute the above code, you will observe that the control never enters the catch block.

Read: Best Online Courses to Learn .NET

Handling Exceptions in Asynchronous Methods Returning a Task Object

If an exception is raised within an asynchronous method returning a Task object, the exception is wrapped into an instance of the AggregateException class and passed back to the calling method. The important point to note here is that, when you await the Task instance, you will be able to get only the first exception, even if several exceptions might have been thrown in your asynchronous method.

Now, refer to the following C# code example that throws exceptions inside an async method that returns a Task instance:

 
public static async Task ThrowMultipleExceptionsAsync()
{
     try
     {
         var firstTask = Task.Run(() => throw new IndexOutOfRangeException
         ("An IndexOutOfRangeException is thrown explicitly."));
         var secondTask = Task.Run(() => throw new InvalidOperationException
         ("An InvalidOperationException is thrown explicitly."));
         await Task.WhenAll(firstTask, secondTask);
     }
     catch (IndexOutOfRangeException ex)
     {
          Console.WriteLine(ex.Message);
     }
     catch (InvalidOperationException ex)
     {
          Console.WriteLine(ex.Message);
     }
}

When an exception is thrown in an async method that returns a Task object, all exceptions are placed on the Task instance and then returned. If there are multiple exceptions and you await the Task the way you normally do, you will get just one exception – the latest one only.

When you execute the above piece of code, you will observe only the first exception message is displayed. In other words, even if two exceptions have been thrown in the async method, you will receive only one of them.

In other words, the await keyword will unwrap the first exception from the Task instance due to which you will get only one exception. To get all the exceptions that are thrown in your async method, you should take advantage of the Exception property of the Task instance as shown in the code example below:

catch
{
      Console.WriteLine("The following exceptions have occurred:-\n");
      AggregateException allExceptions = allTasks.Exception;

     foreach (var ex in allExceptions.InnerExceptions)
     {
         Console.WriteLine(ex.GetType().ToString());
     }
}

Note how the aggregate exception has been used here to retrieve an aggregate of all exceptions that have occurred.

Here is the complete source code of the updated version of our async method for your reference:

public static async Task ThrowMultipleExceptionsAsync()
{
Task allTasks = null;
  try
  {
       var firstTask = Task.Run(() => throw new IndexOutOfRangeException
       ("An IndexOutOfRangeException is thrown explicitly."));
       var secondTask = Task.Run(() => throw new InvalidOperationException
       ("An InvalidOperationException is thrown explicitly."));
       allTasks = Task.WhenAll(firstTask, secondTask);
       await allTasks;
  }
 catch
 {
      Console.WriteLine("The following exceptions have occurred:-\n");
      AggregateException allExceptions = allTasks.Exception;

     foreach (var ex in allExceptions.InnerExceptions)
     {
         Console.WriteLine(ex.GetType().ToString());
     }
   }
}

The Exception property of the Task instance returns an AggregateException reference that contains all exceptions that have been thrown in your asynchronous method.

Final Thoughts on Exception Handling Asynchronous Methods

Exception handling refers to the approach adopted to handle runtime errors in an application. Asynchronous programming has several benefits but it should be noted that the error handling semantics for sync and async methods are different. You must be aware of how you can retrieve exception instances when multiple exceptions are thrown by one or more async methods in your application.

AggregateException.Handle allows programmers to handle certain exceptions while ignoring those you do not want to handle. You can also handle nested AggregateExceptions using the InnerExceptions property of the parent object.

Read more C# programming tutorials and software development tips.

Joydip Kanjilal
Joydip Kanjilal
A Microsoft Most Valuable Professional in ASP.NET, Speaker, and Author of several books and articles. More than 25 years of experience in IT with more than 18 years in Microsoft .NET and its related technologies. He was selected as a Community Credit Winner at http://www.community-credit.com several times. He has authored 8 books and more than 500 articles in some of the most reputed sites worldwide including MSDN, Info World, CodeMag, Tech Beacon, Tech Target, Developer, CodeGuru, and more.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read