Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distinguish timeout from user cancellation

HttpClient has a builtin timeout feature (despite being all asynchronous, i.e. timeouts could be considered orthogonal to the http request functionality and thus be handled by generic asynchronous utilities, but that aside) and when the timeout kicks in, it'll throw a TaskCanceledException (wrapped in an AggregateException).

The TCE contains a CancellationToken that equals CancellationToken.None.

Now if I provide HttpClient with a CancellationToken of my own and use that to cancel the operation before it finishes (or times out), I get the exact same TaskCanceledException, again with a CancellationToken.None.

Is there still a way, by looking only at the exception thrown, to figure out whether a timeout canceled the request, without having to make my own CancellationToken accessible to the code that checks the exception?

P.S. Could this be a bug and CancellationToken got somehow wrongly fixed to CancellationToken.None? In the cancelled using custom CancellationToken case, I'd expect TaskCanceledException.CancellationToken to equal that custom token.

Edit To make the problem a bit more clear, with access to the original CancellationTokenSource, it is easy to distinguish timeout and user cancellation:

origCancellationTokenSource.IsCancellationRequested == true

Getting the CancellationToken from the exception though gives the wrong answer:

((TaskCanceledException) e.InnerException).CancellationToken.IsCancellationRequested == false

Here a minimal example, due to popular demand:

public void foo() {     makeRequest().ContinueWith(task =>     {         try         {             var result = task.Result;             // do something with the result;         }         catch (Exception e)         {             TaskCanceledException innerException = e.InnerException as TaskCanceledException;             bool timedOut = innerException != null && innerException.CancellationToken.IsCancellationRequested == false;              // Unfortunately, the above .IsCancellationRequested             // is always false, no matter if the request was             // cancelled using CancellationTaskSource.Cancel()             // or if it timed out         }     }); }  public Task<HttpResponseMessage> makeRequest() {     var cts = new CancellationTokenSource();     HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };     HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "url");      passCancellationTokenToOtherPartOfTheCode(cts);     return client.SendAsync(httpRequestMessage, cts.Token); } 
like image 243
Evgeniy Berezovsky Avatar asked Oct 01 '12 02:10

Evgeniy Berezovsky


2 Answers

The accepted answer is certainly how this should work in theory, but unfortunately in practice IsCancellationRequested does not (reliably) get set on the token that is attached to the exception:

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

like image 124
Todd Menier Avatar answered Oct 14 '22 10:10

Todd Menier


Yes, they both return the same exception (possibly because of timeout internally using a token too) but it can be easily figured out by doing this:

   catch (OperationCanceledException ex)             {                 if (token.IsCancellationRequested)                 {                     return -1;                 }                  return -2;             } 

so basically if you hit the exception but your token is not cancelled, well it was a regular http timeout

like image 28
Francisco Noriega Avatar answered Oct 14 '22 10:10

Francisco Noriega