取消 HttpClient 请求 - 为什么 TaskCanceledException.CancellationToken.IsCancellationRequested 是错误的?

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

给定以下代码:

var cts = new CancellationTokenSource();

try 
{
    // get a "hot" task
    var task = new HttpClient().GetAsync("http://www.google.com", cts.Token);

    // request cancellation
    cts.Cancel();

    await task;

    // pass:
    Assert.Fail("expected TaskCanceledException to be thrown");
}
catch (TaskCanceledException ex) 
{
    // pass:
    Assert.IsTrue(cts.Token.IsCancellationRequested,
        "expected cancellation requested on original token");

    // fail:
    Assert.IsTrue(ex.CancellationToken.IsCancellationRequested,
        "expected cancellation requested on token attached to exception");
}

我希望 ex.CancellationToken.IsCancellationRequested 在 catch 块内 true,但事实并非如此。我是不是误会了什么?

之所以如此,是因为 HttpClient 在内部(在 SendAsync 中)使用 TaskCompletionSource 来表示 async 操作。它 returns TaskCompletionSource.Task 这就是你 await 的任务。

然后它调用 base.SendAsync 并在返回的任务上注册一个延续,cancels/completes/faults 相应地 TaskCompletionSource 的任务。

在取消的情况下,它使用 TaskCompletionSource.TrySetCanceled 将取消的任务与新的 CancellationToken (default(CancellationToken)) 相关联。

您可以通过查看 TaskCanceledException 来了解这一点。在 ex.CancellationToken.IsCancellationRequested 之上 false ex.CancellationToken.CanBeCanceled 也是 false,这意味着此 CancellationToken 永远无法取消,因为它不是使用 [=27 创建的=].


IMO 它应该使用 TaskCompletionSource.TrySetCanceled(CancellationToken) instead. That way the TaskCompletionSource will be associated with the CancellationToken passed in by the consumer and not simply the default CancellationToken. I think it's a bug (though a minor one) and I submitted an issue on connect 关于它。

我将超时设置为无限以禁用它,然后传入我自己的取消令牌。

using(CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(timespan/timeout))
...
catch(OperationCanceledException e)
{
if(!cancelAfterDelay.Token.IsCancellationRequested)
throw new TimeoutException($"An HTTP request to {request.Uri} timed out ({(int)requestTimeout.TotalSeconds} seconds)");
else
throw;
}

@Bengie 这对我不起作用。我不得不稍微改变一下。 IsCancellationRequested 总是返回 true,所以我不能依赖它。

这对我有用:

using (CancellationTokenSource cancelAfterDelay = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)))
{
    DateTime startedTime = DateTime.Now;

    try
    {
        response = await request.ExecuteAsync(cancelAfterDelay.Token);
    }
    catch (TaskCanceledException e)
    {
        DateTime cancelledTime = DateTime.Now;
        if (startedTime.AddSeconds(timeout-1) <= cancelledTime)
        {
            throw new TimeoutException($"An HTTP request to {request.Url} timed out ({timeout} seconds)");
        }
        else
            throw;
    }
}
return response;