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.
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:
- System.AggregateException: One or more errors occurred.
- A task was canceled
- 'System.AggregateException' occurred in mscorlib.dll
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:
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.
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