Distinguish timeout from user cancellation

北城余情 提交于 2019-12-17 18:08:10

问题


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);
}

回答1:


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




回答2:


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?



来源:https://stackoverflow.com/questions/12666922/distinguish-timeout-from-user-cancellation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!