System.AggregateException

Represents one or more errors that occur during application execution.

Minimum version: >= 4.0 >= Core 1.0

Statistics

13
elmah.io logo 15
12

How to handle it

try
{

}
catch (System.AggregateException e)
{

}
try
{

}
catch (System.AggregateException e) when (e.Message.Contains("something"))
{

}
try
{

}
catch (System.AggregateException e) when (LogException(e))
{

}

private static bool LogException(Exception e)
{
    logger.LogError(...);
    return false;
}

How to avoid it

We haven't written anything about avoiding this exception yet. Got a good tip on how to avoid throwing System.AggregateException? Feel free to reach out through the support widget in the lower right corner with your suggestions.

Links

YouTube videos

Possible fixes from StackOverflow

Instead of implementing retry functionality that wraps the HttpClient, consider constructing the HttpClient with a HttpMessageHandler that performs the retry logic internally. For example:

public class RetryHandler : DelegatingHandler
{
    // Strongly consider limiting the number of retries - "retry forever" is
    // probably not the most user friendly way you could respond to "the
    // network cable got pulled out."
    private const int MaxRetries = 3;

    public RetryHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    { }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;
        for (int i = 0; i < MaxRetries; i++)
        {
            response = await base.SendAsync(request, cancellationToken);
            if (response.IsSuccessStatusCode) {
                return response;
            }
        }

        return response;
    }
}

public class BusinessLogic
{
    public void FetchSomeThingsSynchronously()
    {
        // ...

        // Consider abstracting this construction to a factory or IoC container
        using (var client = new HttpClient(new RetryHandler(new HttpClientHandler())))
        {
            myResult = client.PostAsync(yourUri, yourHttpContent).Result;
        }

        // ...
    }
}

As the message says, you have a task which threw an unhandled exception.

Turn on Break on All Exceptions (Debug, Exceptions) and rerun the program.
This will show you the original exception when it was thrown in the first place.


(comment appended): In VS2015 (or above). Select Debug > Options > Debugging > General and unselect the "Enable Just My Code" option.

ASP.NET Core 2.1 Answer

ASP.NET Core 2.1 added support for Polly directly. Here UnreliableEndpointCallerService is a class which accepts a HttpClient in its constructor. Failed requests will retry with an exponential back-off so that the next retry takes place in an exponentially longer time after the previous one:

services
    .AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(
        x => x.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)));

Also, consider reading my blog post "Optimally Configuring HttpClientFactory".

Other Platforms Answer

This implementation uses Polly to retry with an exponential back-off so that the next retry takes place in an exponentially longer time after the previous one. It also retries if a HttpRequestException or TaskCanceledException is thrown due to a timeout. Polly is much easier to use than Topaz.

public class HttpRetryMessageHandler : DelegatingHandler
{
    public HttpRetryMessageHandler(HttpClientHandler handler) : base(handler) {}

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken) =>
        Policy
            .Handle<HttpRequestException>()
            .Or<TaskCanceledException>()
            .OrResult<HttpResponseMessage>(x => !x.IsSuccessStatusCode)
            .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt)))
            .ExecuteAsync(() => base.SendAsync(request, cancellationToken));
}

using (var client = new HttpClient(new HttpRetryMessageHandler(new HttpClientHandler())))
{
    var result = await client.GetAsync("http://example.com");
}

The difference here comes from using token.ThrowIfCancellationRequested(). This method checks for cancellation and if requested throws OperationCanceledException specifically and not TaskCanceledException (understandable as CancellationToken isn't exclusive to the TPL). You can look at the reference source and see that it calls this method:

private void ThrowOperationCanceledException()
{
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
}

"Regular" cancellation though will indeed generate a TaskCanceledException. You can see that by cancelling the token before the task had a chance to start running:

cancellationTokenSource.Cancel();
var task = Task.Run(() => { }, cancellationTokenSource.Token);
try
{
    await task; 
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
}

Output:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Sandbox.Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

Traditional .Net methods usually don't use CancellationToken.ThrowIfCancellationRequested for async API as this is only appropriate when offloading work to another thread. These methods are for inherently asynchronous operations so cancellation is monitored using CancellationToken.Register (or the internal InternalRegisterWithoutEC).

The current answers won't work as expected in all cases, specifically in the very common case of request timeout (see my comments there).

In addition, they implement a very naive retry strategy - many times you'd want something a bit more sophosticated, such as exponential backoff (which is the default in the Azure Storage Client API).

I stumbled upon TOPAZ while reading a related blog post (also offering the misguided internal retry approach). Here's what I came up with:

// sample usage: var response = await RequestAsync(() => httpClient.GetAsync(url));
Task<HttpResponseMessage> RequestAsync(Func<Task<HttpResponseMessage>> requester)
{
    var retryPolicy = new RetryPolicy(transientErrorDetectionStrategy, retryStrategy);
    //you can subscribe to the RetryPolicy.Retrying event here to be notified 
    //of retry attempts (e.g. for logging purposes)
    return retryPolicy.ExecuteAsync(async () =>
    {
        HttpResponseMessage response;
        try
        {
            response = await requester().ConfigureAwait(false);
        }
        catch (TaskCanceledException e) //HttpClient throws this on timeout
        {
            //we need to convert it to a different exception
            //otherwise ExecuteAsync will think we requested cancellation
            throw new HttpRequestException("Request timed out", e);
        }
        //assuming you treat an unsuccessful status code as an error
        //otherwise just return the respone here
        return response.EnsureSuccessStatusCode(); 
    });
}

Note the requester delegate parameter. It should not be an HttpRequestMessage since you can't send the same request multiple times. As for the strategies, that depends on your use case. For example, a transient error detection strategy could be as simple as:

private sealed class TransientErrorCatchAllStrategy : ITransientErrorDetectionStrategy
{
    public bool IsTransient(Exception ex)
    {
        return true;
    }
}

As for the retry strategy, TOPAZ offers three options:

  1. FixedInterval
  2. Incremental
  3. ExponentialBackoff

For example, here's the TOPAZ equivalent of what the Azure Client Storage Library uses for default:

int retries = 3;
var minBackoff = TimeSpan.FromSeconds(3.0);
var maxBackoff = TimeSpan.FromSeconds(120.0);
var deltaBackoff= TimeSpan.FromSeconds(4.0);
var strategy = new ExponentialBackoff(retries, minBackoff, maxBackoff, deltaBackoff);

For more information see http://msdn.microsoft.com/en-us/library/hh680901(v=pandp.50).aspx

EDIT Note that if your request contains an HttpContent object, you'll have to regenerate it every time as that will be disposed by HttpClient as well (thanks for catching that Alexandre Pepin). For example () => httpClient.PostAsync(url, new StringContent("foo"))).