Отмена HttpClient не уничтожает базовый вызов TCP

Я пытаюсь установить тайм-аут по умолчанию для моих вызовов HttpClient на 5 секунд.

Я сделал это через CancellationTokenSource.

Вот подходящий фрагмент кода:

var cancellationToken = new CancellationTokenSource();
cancellationToken.CancelAfter(TimeSpan.FromSeconds(5));
var result = _httpClient.SendAsync(request, cancellationToken.Token);

Работает так, как я ожидал, с точки зрения вызывающего кода, получающего ошибку «Задача была отменена» (я тестировал в консольном приложении .NET 4.7), но я заметил в Fiddler, что запрос все еще выполнялся в течение 1 минуты, пока он, наконец, не сдался:

введите описание изображения здесь

Может кто-нибудь объяснить такое поведение?

Я ожидаю, что базовый запрос также будет отменен при срабатывании отмены.

_httpClient создается как new HttpClient { BaseAddress = baseAddress }

Я знаю, что есть параметр Timeout, но не уверен, следует ли мне использовать его или токены отмены? Я предполагаю, что Timeout предназначен для случаев, не связанных с async / await?


person RPM1984    schedule 07.08.2017    source источник
comment
В HTTP 1.1 нет возможности отменить это действие запроса, за исключением разрыва всего соединения - что, как я предполагаю, HttpClient не делает для поддержки повторного использования keepalive / соединения.   -  person Damien_The_Unbeliever    schedule 07.08.2017
comment
@Damien_The_Unbeliever ах ... конечно .. совсем забыл про Keep-Alive. Кстати, вы можете установить Keep-Alive в false в HTTP-клиенте (по умолчанию - true, я уверен, по уважительной причине)   -  person RPM1984    schedule 08.08.2017
comment
@Damien_The_Unbeliever, но, сказав это, - если мы не можем отменить базовый запрос (не соединение, а запрос), то в чем смысл токенов отмены? Просто сделать так, чтобы клиент (браузер или в моем примере консольного приложения) больше не заботился о результате? (но запрос все еще может выполняться в фоновом режиме)   -  person RPM1984    schedule 08.08.2017
comment
Как я уже сказал, в HTTP нет концепции отмены. Даже если вы разорвете свое соединение, нет никакой гарантии, прервет ли сервер свою обработку или завершит свою работу. Я бы сказал, что в этом случае все, что вы получаете, - это возможность перестать ждать ответа, а не возможность прервать обработку (что, как я уже сказал, не определено в HTTP)   -  person Damien_The_Unbeliever    schedule 08.08.2017
comment
что произойдет, если вы вручную удалите объект httpClient?   -  person David Haim    schedule 08.08.2017
comment
Если вы также контролируете сервер, то вы можете передать тайм-аут серверу в качестве параметра, и он сделает это самостоятельно.   -  person Ben    schedule 15.10.2018


Ответы (2)


Как сказал Дэмиен в комментариях, HttpClient повторно использует соединения в максимально возможной степени, отсюда и причина, по которой соединение не закрывается при отмене.

При отмене такого запроса HttpClient просто перестанет отправлять / получать данные на / с другого конца. Он не отправит ничего, чтобы сообщить другому концу, что он был отменен. Таким образом, время ожидания в 1 минуту зависит от поведения другого конца вашего соединения.

Кроме того, если вы хотите отменять каждый запрос через 5 секунд, вы также можете установить для свойства Timeout _httpClient значение TimeSpan.FromSeconds(5). Поведение будет точно таким же (будет выдан TaskCanceledException, если другой конец не ответит в течение 5 секунд).

person huysentruitw    schedule 08.08.2017

Если кому-то интересно, вы можете попробовать следующий подход к применению собственного тайм-аута для HttpClient запроса. Кажется, это работает для меня, ограничивая SendAsync() до 2 секунд и немедленно возвращаясь, когда происходит тайм-аут:

private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, TimeSpan? timeout = null)
{
    if (timeout is null)
    {
        return await _httpClient.SendAsync(request);
    }
    else
    {
        using (var cts = new CancellationTokenSource(timeout.Value))
        {
            var sendTask = _httpClient.SendAsync(request);

            while (!sendTask.IsCompleted)
            {
                cts.Token.ThrowIfCancellationRequested();
                await Task.Delay(10).ConfigureAwait(false);
            }

            return await sendTask.ConfigureAwait(false);
        }
    }
}
person Martin Lottering    schedule 15.10.2018