取消HttpClient请求 - 为什么TaskCanceledException.CancellationToken.IsCancellationRequested为false?

时间:2015-03-28 15:44:31

标签: c# .net async-await dotnet-httpclient cancellationtokensource

给出以下代码:

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,但事实并非如此。我误解了什么吗?

3 个答案:

答案 0 :(得分:41)

就是这种情况,因为HttpClient内部(SendAsync)正在使用TaskCompletionSource来表示async操作。它返回TaskCompletionSource.Task,这是您await的任务。

然后调用base.SendAsync并在返回的任务上注册一个延续,相应地取消/完成/完成TaskCompletionSource的任务。

如果取消,则会使用TaskCompletionSource.TrySetCanceled将已取消的任务与新CancellationTokendefault(CancellationToken))相关联。

您可以通过查看TaskCanceledException来查看。 ex.CancellationToken.IsCancellationRequested false ex.CancellationToken.CanBeCanceled之上的false也是CancellationToken,这意味着永远不能取消此CancellationTokenSource,因为它不是使用{{1}创建的}}


IMO应该使用TaskCompletionSource.TrySetCanceled(CancellationToken)代替。这样,TaskCompletionSource将与消费者传入的CancellationToken相关联,而不仅仅是默认的CancellationToken。我认为这是一个错误(虽然是一个小错误),我提交了一个issue on connect

答案 1 :(得分:1)

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

答案 2 :(得分:0)

我将超时设置为Infinite以将其禁用,然后我传入了自己的取消令牌。

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