Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancelling an HttpClient Request - Why is TaskCanceledException.CancellationToken.IsCancellationRequested false?

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 be true inside the catch block, but it is not. Am I misunderstanding something?

like image 726
Todd Menier Avatar asked Mar 28 '15 15:03

Todd Menier


People also ask

How do I cancel a CancellationToken?

CancellationToken is immutable and must be canceled by calling CancellationTokenSource. cancel() on the CancellationTokenSource that creates it. It can only be canceled once. If canceled, it should not be passed to future operations.

What is task Cancelled exception?

There's 2 likely reasons that a TaskCanceledException would be thrown: Something called Cancel() on the CancellationTokenSource associated with the cancellation token before the task completed. The request timed out, i.e. didn't complete within the timespan you specified on HttpClient.

How does C# CancellationToken work?

A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource. Token property.


1 Answers

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.

like image 68
i3arnon Avatar answered Sep 27 '22 20:09

i3arnon