Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?
Solution 1
That's the case because HttpClient
internally (in SendAsync
) is using a TaskCompletionSource
to represent the async
operation. It returns TaskCompletionSource.Task
and that's the task you await
on.
It then calls base.SendAsync
and registers a continuation on the returned task that cancels/completes/faults the TaskCompletionSource
's task accordingly.
In the case of cancellation it uses TaskCompletionSource.TrySetCanceled
which associates the canceled task with a new CancellationToken
(default(CancellationToken)
).
You can see that by looking at the TaskCanceledException
. On top of ex.CancellationToken.IsCancellationRequested
being false
ex.CancellationToken.CanBeCanceled
is also false
, meaning that this CancellationToken
can never be canceled as it wasn't created using a CancellationTokenSource
.
IMO it should be using 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 about it.
Solution 2
@Bengie This didn't work for me. I had to alter it a little. IsCancellationRequested always returned true so i couldn't rely on that.
This worked for me:
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;
Todd Menier
OSS projects: Flurl - Fluent URL builder and wrist-friendly HTTP client library for .NET. AsyncPoco - Fully asynchronous fork of PetaPoco micro-ORM for .NET.
Updated on June 03, 2020Comments
-
Todd Menier almost 4 years
Given the following code:
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"); }
I would expect
ex.CancellationToken.IsCancellationRequested
to betrue
inside the catch block, but it is not. Am I misunderstanding something?