Any way to differentiate Cancel and Timeout

I have a code that checks some data by making calls to a number of other services. I start all the calls in parallel, and then wait until at least one of them completes. If any of the requests fail, I don't care about the result of other calls.

I make calls using the HttpClient , and I passed the HttpMessageHandler in which a bunch of logs are running. Essentially:

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage response = null; try { response = await base.SendAsync(request, cancellationToken); } catch (OperationCanceledException ex) { LogTimeout(...); throw; } catch (Exception ex) { LogFailure(...); throw; } finally { LogComplete(...); } return response; } 

There is no part that I am having problems with when I cancel requests. When I cancel a request, I do it on purpose, so I donโ€™t want it to register as a timeout, but there seems to be no difference between the cancellation and the real timeout.

Is there any way to do this?

Edit: I need to clarify this, a bit. A service making calls in parallel passes through a CancellationTokens with a timeout:

 var ct = new CancellationTokenSource(TimeSpan.FromSeconds(2)); 

Therefore, when the server responds to requests for more than two seconds, I get an OperationCanceledException , and if I manually cancel the token (say, because the other server returned an error after 1 second), I still get an OperationCanceledException . Ideally, I could look at CancellationToken.IsCancellationRequested to determine if it was canceled due to a timeout, unlike the explicitly requested cancellation, but it looks like you get the same value no matter how it was canceled.

+6
source share
2 answers

If you want to distinguish between two types of cancellation, you need to use two different cancellation tokens. There is no other way. This is not so difficult as they can be connected - a little inconvenient.

The cleanest way to write this IMO is to move the timeout code to the SendAsync method instead of the calling method:

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { cts.CancelAfter(TimeSpan.FromSeconds(2)); try { return await base.SendAsync(request, cancellationToken); } catch (OperationCanceledException ex) { if (cancellationToken.IsCancellationRequested) return null; LogTimeout(...); throw; } catch (Exception ex) { LogFailure(...); throw; } finally { LogComplete(...); } } } 

If you do not want to translate the timeout code to SendAsync , you will also need to log outside this method.

+5
source

If the exceptions do not indicate the difference between the two cases, you will need to check with either Task or CancellationToken to see if it has been canceled.

I would lean to the question that Task , which has the IsCanceled property, would return true if an unhandled OperationCanceledException base.SendAsync (with a CancellationToken.ThrowIfCancellationRequested probability inside base.SendAsync ). Something like that...

 HttpResponseMessage response = null; Task sendTask = null; try { sendTask = base.SendAsync(request, cancellationToken); await sendTask; } catch (OperationCanceledException ex) { if (!sendTask.IsCancelled) { LogTimeout(...); throw; } } 

EDIT

In response to updating the question, I wanted to update my answer. You correctly cancel whether it is specifically requested for the CancellationTokenSource or if it is timed out, will lead to an accurate result. If you decompile the CancellationTokenSource , you will see that for the timeout, it simply sets the Timer callback, which will explicitly call CancellationTokenSource.Cancel when the timeout is reached, so both methods will eventually call the same Cancel method.

I think that if you want to tell the difference, you need to get from the CancellationTokenSource (this is not a sealed class), and then add your own cancellation method that sets a flag so that you know that you explicitly canceled the operation and not give it time.

This is unfortunate, since you will have both your custom cancel method and the original Cancel method, and you will definitely need to use it. You can get away with your custom logic just by hiding the existing Cancel operation with something like this:

 class CustomCancellationTokenSource : CancellationTokenSource { public bool WasManuallyCancelled {get; private set;} public new void Cancel() { WasManuallyCancelled = true; base.Cancel(); } } 

I think hiding the base method will work, you can give it a chance and find out.

+2
source

All Articles