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?
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;