32

给定以下代码:

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.IsCancellationRequestedtruecatch 块内,但事实并非如此。我是不是误会了什么?

4

3 回答 3

48

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

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

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

您可以通过查看TaskCanceledException. 最重要的ex.CancellationToken.IsCancellationRequestedfalse ex.CancellationToken.CanBeCanceledis false,这意味着它CancellationToken永远不能被取消,因为它不是使用 a 创建的CancellationTokenSource


IMO 它应该TaskCompletionSource.TrySetCanceled(CancellationToken)改为使用。这样,TaskCompletionSource将与CancellationToken消费者传入的值相关联,而不仅仅是默认值CancellationToken。我认为这是一个错误(虽然是一个小错误),我在 connect 上提交了一个关于它的问题。

于 2015-03-28T16:04:10.490 回答
3

@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;
于 2019-06-02T15:36:15.660 回答
0

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

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;
}
于 2019-03-05T18:27:47.393 回答