Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?
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.
@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;