1

HttpClient async methods (e.g. PostAsync, GetAsync etc.), if an exception is thrown, returns an AggregateException. Now an aggregate exception can contain a list of inner exceptions. My question is can someone provide an example where one of the async methods on an http client leads to more than one inner exception?

Is it safe to assume that although there is potentially a list of inner exceptions, in most cases you will only get one inner exception?

I'm aware of why they are being throw and how to handle it.

So to make this clearer, is it possible for a single call to an http client async method that throws an aggregate exception to have more than one exception in it's list of inner exceptions?

Mayoweezy
  • 537
  • 1
  • 8
  • 21
  • 1
    The `AggregationException` is thrown because a `Task` is being returned, which may encapsulate one or more exceptions. If you `await` on those async methods, you can avoid having to guess if one or more exceptions occurred inside your code. – Yuval Itzchakov Dec 23 '14 at 11:29
  • Sorry if am not being clear, I'm aware of why it's being thrown and how to handle it. I just wanted to know if anyone had an example for the above scenario. Just need it for teaching purposes as I could not create one personally. – Mayoweezy Dec 23 '14 at 11:34
  • 2
    I wouldn't depend on it throwing one exception. It's probably the common use-case, but you may always be surprised. If it's for teaching purposes, it's probably reasonable to say that *most of the time* you'll encounter a single exception, but they should never depend on it. They can call `AggregationException.Flatten` (In case any inner exceptions were of type `AggregationException`) and iterate the given enumerable. – Yuval Itzchakov Dec 23 '14 at 11:39
  • I did, friend suggested I provide an example, was stumped and curious, hence my question – Mayoweezy Dec 23 '14 at 11:42

2 Answers2

1

If you await Task.WhenAll(), and multiple tasks in that WhenAll throw exceptions, their exceptions will be aggregated.

The TPL throwing an aggregate exception like this is by design, so that exceptions for one task are not lost just because exceptions happened in another.

example:

var failTask1 = Task.Run(() => { throw new Exception("Example 1"); });
var failTask2 = Task.Run(() => { throw new Exception("Another example"); });
await Task.WhenAll(failTask1, failTask2);
Steve Lillis
  • 3,263
  • 5
  • 22
  • 41
1

If you look inside HttpClient.SendAsync (which is the inner method being used to send all requests), you'll see that the Task being created is a simple TaskCompletionSource<HttpResponseMessage>. Inside the calling method, it sets this.SetTaskFaulted multiple times, but always inside an if-else block. What could potentially happen is that when SetTaskFaulted sets the exception, it could set off another exception:

private void SetTaskFaulted(HttpRequestMessage request, CancellationTokenSource cancellationTokenSource, TaskCompletionSource<HttpResponseMessage> tcs, Exception e, TimerThread.Timer timeoutTimer)
{
    this.LogSendError(request, cancellationTokenSource, "SendAsync", e);
    tcs.TrySetException(e);
    HttpClient.DisposeCancellationTokenAndTimer(cancellationTokenSource, timeoutTimer);
}

DisposeCancellationTokenAndTimer internally disposes the CancellationToken, and inside the finally block disposes the timer:

private static void DisposeCancellationTokenAndTimer(CancellationTokenSource cancellationTokenSource, TimerThread.Timer timeoutTimer)
{
    try
    {
        cancellationTokenSource.Dispose();
    }
    catch (ObjectDisposedException)
    {
    }
    finally
    {
        HttpClient.DisposeTimer(timeoutTimer);
    }
}

The timer could potentially throw an exception from its Dispose method. Although im sure that's very rare.

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
    if (request == null)
    {
        throw new ArgumentNullException("request");
    }
    this.CheckDisposed();
    HttpClient.CheckRequestMessage(request);
    this.SetOperationStarted();
    this.PrepareRequestMessage(request);
    CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.pendingRequestsCts.Token);
    TimerThread.Timer timeoutTimer = this.SetTimeout(linkedCts);
    TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
    try
    {
        base.SendAsync(request, linkedCts.Token).ContinueWithStandard(delegate(Task<HttpResponseMessage> task)
        {
            try
            {
                this.DisposeRequestContent(request);
                if (task.IsFaulted)
                {
                    this.SetTaskFaulted(request, linkedCts, tcs, task.Exception.GetBaseException(), timeoutTimer);
                }
                else
                {
                    if (task.IsCanceled)
                    {
                        this.SetTaskCanceled(request, linkedCts, tcs, timeoutTimer);
                    }
                    else
                    {
                        HttpResponseMessage result = task.Result;
                        if (result == null)
                        {
                            this.SetTaskFaulted(request, linkedCts, tcs, new InvalidOperationException(SR.net_http_handler_noresponse), timeoutTimer);
                        }
                        else
                        {
                            if (result.Content == null || completionOption == HttpCompletionOption.ResponseHeadersRead)
                            {
                                this.SetTaskCompleted(request, linkedCts, tcs, result, timeoutTimer);
                            }
                            else
                            {
                                this.StartContentBuffering(request, linkedCts, tcs, result, timeoutTimer);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                if (Logging.On)
                {
                    Logging.Exception(Logging.Http, this, "SendAsync", ex);
                }
                tcs.TrySetException(ex);
            }
        });
    }
    catch
    {
        HttpClient.DisposeTimer(timeoutTimer);
        throw;
    }
    return tcs.Task;
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321