【问题标题】:HttpClient async methods, aggregate exceptionsHttpClient 异步方法,聚合异常
【发布时间】:2014-12-23 11:27:09
【问题描述】:

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

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

我知道他们被扔的原因以及如何处理。

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

【问题讨论】:

  • AggregationException 被抛出是因为 Task<T> 被返回,它可能封装了一个或多个异常。如果您在这些异步方法上 await,则可以避免猜测代码中是否发生了一个或多个异常。
  • 对不起,如果不清楚,我知道为什么会抛出它以及如何处理它。我只是想知道是否有人有上述场景的示例。只需要它用于教学目的,因为我无法亲自创建。
  • 我不会依赖它抛出一个异常。这可能是常见的用例,但您可能总是会感到惊讶。如果是出于教学目的,可以说大多数时候你会遇到一个异常,但他们永远不应该依赖它。他们可以调用AggregationException.Flatten(如果任何内部异常的类型为AggregationException)并迭代给定的枚举。
  • 我做了,朋友建议我提供一个例子,被难住和好奇,因此我的问题

标签: c# dotnet-httpclient aggregateexception


【解决方案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;

【讨论】:

    【解决方案2】:

    如果您 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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 2016-10-13
      • 1970-01-01
      相关资源
      最近更新 更多