取消 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;
给定以下代码:
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;