1

HttpClient 异步方法(例如 PostAsync、GetAsync 等),如果抛出异常,则返回 AggregateException。现在,聚合异常可以包含内部异常列表。我的问题是有人可以提供一个示例,其中 http 客户端上的一种异步方法导致多个内部异常?

假设尽管可能存在内部异常列表,但在大多数情况下您只会得到一个内部异常,是否安全?

我知道他们为什么被扔掉以及如何处理它。

因此,为了更清楚地说明这一点,是否有可能对引发聚合异常的 http 客户端异步方法的一次调用在其内部异常列表中包含多个异常?

4

2 回答 2

1

如果您 await Task.WhenAll(),并且该 WhenAll 中的多个任务抛出异常,则它们的异常将被聚合。

TPL 抛出这样的聚合异常是设计使然,因此一个任务的异常不会因为另一个任务发生异常而丢失。

例子:

var failTask1 = Task.Run(() => { throw new Exception("Example 1"); });
var failTask2 = Task.Run(() => { throw new Exception("Another example"); });
await Task.WhenAll(failTask1, failTask2);
于 2014-12-23T11:33:35.633 回答
1

如果您查看内部HttpClient.SendAsync(这是用于发送所有请求的内部方法),您会发现Task正在创建的是一个简单的TaskCompletionSource<HttpResponseMessage>. 在调用方法内部,它设置this.SetTaskFaulted了多次,但总是在一个if-else块内。可能发生的情况是,当设置异常时,它可能会引发另一个异常SetTaskFaulted

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内部配置CancellationToken,finally块内部配置定时器:

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

计时器可能会从其Dispose方法中抛出异常。虽然我确信这是非常罕见的。

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;
于 2014-12-23T12:04:16.763 回答