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

C# 取消HttpClient请求-为什么TaskCanceledException.CancellationToken.IsCancellationRequested为false?,c#,.net,async-await,dotnet-httpclient,cancellationtokensource,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.Fa

给定以下代码:

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

我希望catch块中的
ex.CancellationToken.IsCancellationRequested
true
,但事实并非如此。我误解了什么吗?

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

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

在取消的情况下,它使用将已取消的任务与新的
CancellationToken
default(CancellationToken)
)关联的

通过查看
TaskCanceledException
可以看到这一点。除了
ex.CancellationToken.IsCancellationRequested
false
ex.CancellationToken.canbecancelled
也是
false
,这意味着此
CancellationToken
不能取消,因为它不是使用
CancellationTokenSource
创建的



依我看,应该改用它。这样,
TaskCompletionSource
将与使用者传入的
CancellationToken
关联,而不仅仅是默认的
CancellationToken
。我认为这是一个bug(虽然只是一个小bug),我提交了一份关于它的报告。

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

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;
}
@班吉 这对我不起作用。我不得不稍微改变一下。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;
使用(CancellationTokenSource cancelAfterDelay=新的CancellationTokenSource(TimeSpan.FromSeconds(timeout)))
{
DateTime startedTime=DateTime.Now;
尝试
{
response=wait request.ExecuteAsync(cancelAfterDelay.Token);
}
捕获(TaskCanceledException e)
{
DateTime CanceledTime=DateTime.Now;

如果(startedTime.AddSeconds(timeout-1)的
ex.CancelationToken
实例等于(ReferenceEqual)cts?文档说明:“如果令牌与已取消的操作关联,则令牌的
CancelationToken.IsCancellationRequested
属性返回
true
”.@Alex:
CancellationToken
是一个结构,因此
ReferenceEquals()
将始终返回false.@AndreasNiedermair:否。我的意思是,如果您有
CancellationToken=cts.token;
并计算
对象。ReferenceEquals(token,token)
(即将
CancellationToken
值与自身进行比较),即使是这样,也会返回
false
,因为值类型在作为
对象
引用传递之前必须装箱,所以装箱的对象总是不同的,即使它们是从相同的值获得的。@usr:他发布的代码有效。我将其复制/粘贴到一个空白的控制台应用程序中(使用自己的方法,因为
Main()
不能是
async
),并且它的行为与报告的完全相同。请注意,此代码具有争用条件。该操作可能已取消,或者可能在取消之前刚刚正常完成。您应该在开始实际工作之前取消令牌(或者确保在取消令牌之前无法完成工作)为了确保不会发生这种情况。回答得很好,+1用于报告问题。我不太确定这是否是次要的,因为我(和)在
HttpClient
@ToddMenier的情况下,依靠它来区分用户取消和超时。这是一个令人惊讶的有趣问题。这也让我想到了这个有趣的问题(我希望如此)发现:嗯,您所考虑的过载是内部的,因此可能有一些原因不应该以这种方式使用它。在
TaskCompletionSource
取消过程中发生了很多有趣的事情(例如,您可以尝试多次取消TCS)TCS和它内部创建的
Task
都与取消令牌无关——这只是那些泄漏的抽象之一。实际上有两种
Task
,作为CPU工作的Task和作为异步I/O的Task,
HttpClient
,使用后者。有时我希望它们实际上是分开的……连接I/Ossue链接404s用于me@StriplingWarrior您打开的问题位于完全相同的位置。无论如何,他们上周回复说,它将在.NET4.7.2中修复