System.AggregateException
Represents one or more errors that occur during application execution.
Minimum version: >= 4.0 >= Core 1.0
Statistics
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;
}
// ...
}
}
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");
}
It is, it's all about understanding the lifetimes of the various elements in play and getting those set correctly.
By default the DbContextFactory
created by the AddDbContextFactory()
extension method has a Singleton lifespan. If you use the AddDbContext()
extension method with it's default settings it will create a DbContextOptions
with a Scoped
lifespan (see the source-code here), and as a Singleton
can't use something with a shorter Scoped
lifespan, an error is thrown.
To get round this, we need to change the lifespan of the DbContextOptions
to also be 'Singleton'. This can be done using by explicitly setting the scope of the DbContextOptions
parameter of AddDbContext()
services.AddDbContext<FusionContext>(options =>
options.UseSqlServer(YourSqlConnection),
optionsLifetime: ServiceLifetime.Singleton);
There's a really good discussion of this on the EF core GitHub repository starting here. It's also well worth having a look at the source-code for DbContextFactory
here.
Alternatively, you can also change the lifetime of the DbContextFactory
by setting the ServiceLifetime parameter in the constructor:
services.AddDbContextFactory<FusionContext>(options =>
options.UseSqlServer(YourSqlConnection),
ServiceLifetime.Scoped);
The options should be configured exactly as you would for a normal DbContext as those are the options that will be set on the DbContext the factory creates.
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.
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
).
Source: Stack Overflow