Отличать тайм-аут от отмены пользователем

HttpClient имеет встроенную функцию тайм-аута (несмотря на то, что все они асинхронны, т.е. тайм-ауты могут считаться ортогональными по отношению к функциям http-запроса и, таким образом, обрабатываются общими асинхронными утилитами, но это в стороне), и когда тайм-аут срабатывает, он выдает TaskCanceledException ( завернутый в AggregateException).

TCE содержит CancellationToken, равное CancellationToken.None.

Теперь, если я предоставлю HttpClient свой собственный CancellationToken и использую его для отмены операции до ее завершения (или истечения времени ожидания), я получу точно такой же TaskCanceledException, снова с CancellationToken.None.

Есть ли еще способ, посмотрев только на выброшенное исключение, чтобы выяснить, отменил ли запрос тайм-аут, без необходимости делать мой собственный CancellationToken доступным для кода, проверяющего исключение?

P.S. Может ли это быть ошибкой, и CancellationToken каким-то образом неправильно исправлено в CancellationToken.None? В случае отменено с использованием пользовательского CancellationToken я ожидаю, что TaskCanceledException.CancellationToken будет равно этому пользовательскому токену.

Изменить. Чтобы сделать проблему немного более ясной, имея доступ к исходному CancellationTokenSource, легко отличить время ожидания от отмены пользователя:

origCancellationTokenSource.IsCancellationRequested == true

Однако получение CancellationToken из исключения дает неправильный ответ:

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

Вот минимальный пример по многочисленным просьбам:

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

person Evgeniy Berezovsky    schedule 01.10.2012    source источник
comment
Пожалуйста, опубликуйте минимальный фрагмент кода, который демонстрирует это поведение.   -  person Richard Cook    schedule 01.03.2013
comment
почему код, запускающий задачу, не обрабатывает исключение? Этот фрагмент кода уже должен иметь CancellationToken, он может управлять этим сценарием, а затем просто генерировать исключение, которое вы хотите, чтобы ваш блок try/catch более высокого уровня получал без TokenSource   -  person Francisco Noriega    schedule 02.03.2013
comment
@FranciscoNoriega Иногда вы хотите разделить код, например. чтобы сделать его многоразовым, а также в асинхронно выполняемом коде. Таким образом, вы в конечном итоге перехватываете исключения в разных местах, а не там, где определен исполняемый код. Я не хочу передавать все задействованное состояние, тем более что исключение, похоже, обеспечивает это состояние, когда оно необходимо, за исключением того, что оно не работает так, как я думаю, должно. Взгляните на урезанный пример, который примерно показывает, как я его использую.   -  person Evgeniy Berezovsky    schedule 05.03.2013
comment
@EugeneBeresovksy, вам удалось решить проблему? У меня похожая ситуация, у меня есть приложение, в котором большая часть обработки исключений выполняется в одном месте.   -  person Arthur Nunes    schedule 29.11.2013
comment
@ArthurNunes Что вы можете сделать, так это создать свой собственный MyTaskCanceledException с флагом. Затем вы перехватываете исходный TaskCanceledException в области, где у вас есть доступ к исходному CancellationTokenSource, и переупаковываете его в собственное исключение, устанавливая флаг с помощью origCancellationTokenSource.IsCancellationRequested.   -  person Evgeniy Berezovsky    schedule 29.11.2013
comment
@EugeneBeresovksy, в конце концов я так и сделал, но вместо этого выбрасываю TimeoutException. Проблема с этим подходом заключается в том, что мне приходится делать это каждый раз, когда я использую HttpClient или создаю свои собственные методы расширения.   -  person Arthur Nunes    schedule 29.11.2013


Ответы (2)


Принятый ответ, безусловно, заключается в том, как это должно работать в теории, но, к сожалению, на практике IsCancellationRequested не устанавливается (надежно) на токен, прикрепленный к исключению:

Отмена запроса HttpClient. Почему TaskCanceledException.CancellationToken.IsCancellationRequested является ложным?

person Todd Menier    schedule 30.03.2015
comment
возможно, вы не сможете использовать прикрепленный токен, но если у вас есть исходный токен (который был передан SendAsync), вы можете его использовать. Или я что-то упускаю? - person Josef Bláha; 29.09.2016
comment
Я вижу, я упускаю из виду, что в вопросе требовалось полагаться только на данные исключения. - person Josef Bláha; 29.09.2016

Да, они оба возвращают одно и то же исключение (возможно, из-за внутреннего тайм-аута с использованием токена), но его можно легко понять, выполнив следующие действия:

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

                return -2;
            }

так что в основном, если вы попали в исключение, но ваш токен не был отменен, ну, это был обычный тайм-аут http

person Francisco Noriega    schedule 01.03.2013
comment
Я выделил посмотрев только на выброшенное исключение в моем вопросе жирным шрифтом и добавил немного больше деталей, чтобы было более понятно, о чем я спрашиваю. - person Evgeniy Berezovsky; 01.03.2013
comment
OperationCanceledException также выбрасывается, если HttpClient удаляется во время ожидания запроса или если вызывается HttpClient.CancelPendingRequests. Эти случаи можно различить, если вы контролируете вызовы этих методов. - person Ernesto; 10.01.2015
comment
в этом ответе отсутствуют подробности... откуда token? это свойство исключения? голосование против, пока вы не улучшите его - person ympostor; 12.01.2017
comment
@ympostor эта информация есть в исходном вопросе. Он предоставляет свой собственный токен, вот откуда он. - person Francisco Noriega; 12.01.2017