【问题标题】:Distinguish timeout from user cancellation区分用户取消超时
【发布时间】:2012-10-01 02:37:00
【问题描述】:

HttpClient 具有内置的超时功能(尽管都是异步的,即超时可以被认为与 http 请求功能正交,因此可以由通用异步实用程序处理,但除此之外)并且当超时开始时,它'会抛出一个TaskCanceledException(包裹在AggregateException中)。

TCE 包含一个等于CancellationToken.NoneCancellationToken

现在,如果我向HttpClient 提供我自己的CancellationToken 并使用它在操作完成(或超时)之前取消操作,我会得到完全相同的TaskCanceledException,再次使用CancellationToken.None

还有没有办法,只查看抛出的异常,来确定超时是否取消了请求,而不必让我自己的CancellationToken 可以访问检查异常?

附:这可能是一个错误,CancellationToken 以某种方式错误地固定为CancellationToken.None?在 使用自定义 CancellationToken 取消 的情况下,我希望 TaskCanceledException.CancellationToken 等于该自定义令牌。

编辑 把问题说清楚一点,访问原CancellationTokenSource,很容易区分超时和用户取消:

origCancellationTokenSource.IsCancellationRequested == true

从异常中获取 CancellationToken 会给出错误的答案:

((TaskCanceledException) e.InnerException).CancellationToken.IsCancellationRequested == false

这里一个小例子,由于大众的需求:

public void foo()
{
    makeRequest().ContinueWith(task =>
    {
        try
        {
            var result = task.Result;
            // do something with the result;
        }
        catch (Exception e)
        {
            TaskCanceledException innerException = e.InnerException as TaskCanceledException;
            bool timedOut = innerException != null && innerException.CancellationToken.IsCancellationRequested == false;

            // Unfortunately, the above .IsCancellationRequested
            // is always false, no matter if the request was
            // cancelled using CancellationTaskSource.Cancel()
            // or if it timed out
        }
    });
}

public Task<HttpResponseMessage> makeRequest()
{
    var cts = new CancellationTokenSource();
    HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };
    HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "url");

    passCancellationTokenToOtherPartOfTheCode(cts);
    return client.SendAsync(httpRequestMessage, cts.Token);
}

【问题讨论】:

  • 请发布一段显示此行为的最小代码。
  • 为什么启动任务的代码没有处理异常?那段代码应该已经有 CancellationToken,它可以管理该场景,然后只抛出您希望更高级别的 try/catch 块接收的异常,而不需要 TokenSource
  • @FranciscoNoriega 有时您想分离代码,例如使其可重用,并在异步执行的代码中。因此,您最终会在与定义执行代码的位置不同的位置捕获异常。我不想传递所有涉及的状态,特别是当异常似乎在需要时提供该状态 - 除非它不像我认为的那样工作。看一下大致展示了我如何使用它的精简示例。
  • @EugeneBeresovksy,你有没有设法解决这个问题?我处于类似情况,我有一个应用程序,其中大部分异常处理都在一个地方完成。
  • @ArthurNunes 你可以做的是创建自己的带有标志的MyTaskCanceledException。然后,您在可以访问原始CancellationTokenSource 的范围内捕获原始TaskCanceledException,并将其重新打包到您自己的异常中,使用origCancellationTokenSource.IsCancellationRequested 设置标志。

标签: c# .net timeout task-parallel-library dotnet-httpclient


【解决方案1】:

公认的答案当然是这个应该在理论上是如何工作的,但不幸的是,在实践中IsCancellationRequested 并没有(可靠地)设置在附加到异常的令牌上:

Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?

【讨论】:

  • 您可能无法使用附加的令牌,但如果您有原始令牌(传递给SendAsync),您可以使用它。还是我错过了什么?
  • 我明白了,我错过了这个问题只需要依赖异常数据。
【解决方案2】:

是的,它们都返回相同的异常(也可能是由于内部使用令牌超时),但可以通过这样做很容易地找出:

   catch (OperationCanceledException ex)
            {
                if (token.IsCancellationRequested)
                {
                    return -1;
                }

                return -2;
            }

所以基本上如果你遇到异常但你的令牌没有被取消,那么这是一个常规的 http 超时

【讨论】:

  • 我在我的问题中只查看抛出的异常,并添加了更多细节以更清楚地说明我在问什么。
  • OperationCanceledException 如果在请求挂起时处理了HttpClient,或者如果调用了HttpClient.CancelPendingRequests,也会抛出。如果您控制对这些方法的调用,则可以区分这些情况。
  • 这个答案缺乏细节......token 来自哪里?它是异常的属性吗?在你改进它之前投反对票
  • @ympostor 原始问题中有信息。他正在提供自己的令牌,这就是它的来源。
猜你喜欢
  • 1970-01-01
  • 2017-09-28
  • 2014-02-13
  • 2017-10-06
  • 2023-03-24
  • 1970-01-01
  • 2012-04-21
  • 2021-04-30
  • 1970-01-01
相关资源
最近更新 更多