Debugging System.AggregateException - even in async code

We are here once again to help you debug common .NET exceptions. This time I want to help you debug the rather generic System.AggregateException. As the name implies, AggregateException is used to batch one or more exceptions together in a single exception. In this post, I'll show you why this exception occurs and how to debug it in your C# code.

Debugging System.AggregateException - even in async code

Causing and handling the error

Let's start by forcing a new AggregateException. The exception is used heavily in .NET's Task libraries, why choosing an example involving tasks is a no-brainer:

Task task1 = Task.Factory.StartNew(() => { throw new ArgumentException(); } );
Task task2 = Task.Factory.StartNew(() => { throw new UnauthorizedAccessException(); } );

try
{
    Task.WaitAll(task1, task2);
}
catch (AggregateException ae)
{
}

In the example above, we spin up two tasks, each throwing an exception. By calling WaitAll we tell .NET to invoke and wait for a result from each of the two tasks. The combined result of the two tasks will be an AggregateException.

You've probably come to this article because of one of the following error messages:

In the following section, I will show you how to debug and fix each.

Debugging the error

When inspecting the exception from the previous example in the debugger, we see the two thrown exceptions in the InnerExceptions property:

Inner exceptions

Just as promised in the name, AggregateException is a wrapper of other exceptions. In the example, both the ArgumentException and the UnauthorizedAccessException is available as inner exceptions.

In some scenarios you would add a catch blog per exception like this:

try
{
    ...
}
catch (ArgumentException ex1)
{
    // Log and re-throw
}
catch (UnauthorizedAccessException ex2)
{
    // Log and swallow
}

This way, you can generate individual error messages to the user, log the exception but only re-throw one of them, or something third. Rather than looping over each exception in the InnerExceptions property, AggregateException provides a handy little helper method named Handle:

try
{
    ...
}
catch (AggregateException ae)
{
    ae.Handle(inner =>
    {
        // Log inner
        ...
        return inner is UnauthorizedAccessException;
    });
}

In the example, the Handle method logs each exception. The func needs to return a bool indicating if each exception has been handled. In this case, we tell the Handle method that the UnauthorizedAccessException is handled, but not the ArgumentException. This will cause the AggregateException to be thrown back to the calling code, but without the UnauthorizedAccessException in the InnerExceptions property (because we marked that exception as already handled).

AggregateExceptions from HttpClient

When dealing with the System.Net.Http.HttpClient class, you may experience the AggregateException. This is mostly when implementing async code using the old API (Wait, ContinueWith, etc.). Let's look at an example:

try
{
    var client = new HttpClient();
    var task = client.GetStringAsync("https://httpstat.us/500");
    task.Wait();
    var result = task.Result;
}
catch (AggregateException ex)
{
    throw ex;
}

The request to https://httpstat.us/500 returns an HTTP status code of 500 which throws a HttpRequestException. I'm using the GetStringAsync method as an example, but the code would look similar if using other async messages like PostAsync. As we saw previously, exceptions from the tasks API are wrapped in an AggregateException, containing the HTTP exception in the InnerExceptions property.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

To get the actual exception, you have a range of options. I'll re-use the need for logging the exception to illustrate. Let's start with the Handle method I showed you in a previous example:

try
{
    ...
}
catch (AggregateException ex)
{
    ex.Handle(inner =>
    {
        if (inner is HttpRequestException)
        {
            // Log the exception
            
            return true;
        }
        
        return false;
    });
}

I'm checking if the inner exception is of type HttpRequestException and in that case tell the Handle method that this exception has been handled. In any other scenario, I tell Handle to re-throw the original exception (by returning false).

Another approach will be to use the ContinueWith method to catch and handle the AggregateException:

var client = new HttpClient();
var task = client
    .GetStringAsync("https://httpstat.us/500")
    .ContinueWith(t =>
    {
        try
        {
            return t.Result;
        }
        catch (AggregateException ex)
        {
            ex.Handle(inner =>
            {
                if (inner is HttpRequestException)
                {
                    // Log the exception

                    return true;
                }

                return false;
            });
        }
        catch (Exception ex)
        {
            throw ex;
        }

        return null;
    });
task.Wait();
var result = task.Result;

I've basically just moved the try/catch from before inside the ContinueWith method.

Finally, if you are on .NET 4.5 (you probably are and if not, you should be) you can use the await keyword:

var client = new HttpClient();
try
{
    var result = await client.GetStringAsync("https://httpstat.us/500");
}
catch (HttpRequestException ex)
{
    // Log the exception
}
catch (Exception ex)
{
    throw ex;
}

Notice how the code catches HttpRequestException instead of AggregateException. This is because .NET automatically unwraps the AggregateException and throws the underlying exception instead. This is what you want in most cases. Yet another benefit of porting existing async code to use the await keyword.

Also make sure to read the other posts in this series: Debugging common .NET exception.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website