С помощью HttpClient.SendAsync использует пул потоков вместо асинхронного ввода-вывода?

таким образом, я копался в реализации HttpClient.SendAsync через отражатель. Я намеренно хотел узнать поток выполнения этих методов и определить, какой API вызывается для выполнения асинхронной работы ввода-вывода.

после изучения различных классов внутри HttpClient, Я видел, что внутренне он использует HttpClientHandler, который является производным от HttpMessageHandler и осуществляет SendAsync метод.

это реализация HttpClientHandler.SendAsync:

protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    if (request == null)
    {
        throw new ArgumentNullException("request", SR.net_http_handler_norequest);
    }

    this.CheckDisposed();
    this.SetOperationStarted();

    TaskCompletionSource<HttpResponseMessage> source = new TaskCompletionSource<HttpResponseMessage>();

    RequestState state = new RequestState 
    {
        tcs = source,
        cancellationToken = cancellationToken,
        requestMessage = request
    };

    try
    {
        HttpWebRequest request2 = this.CreateAndPrepareWebRequest(request);
        state.webRequest = request2;
        cancellationToken.Register(onCancel, request2);

        if (ExecutionContext.IsFlowSuppressed())
        {
            IWebProxy proxy = null;

            if (this.useProxy)
            {
                proxy = this.proxy ?? WebRequest.DefaultWebProxy;
            }
            if ((this.UseDefaultCredentials || (this.Credentials != null)) || ((proxy != null) && (proxy.Credentials != null)))
            {
                this.SafeCaptureIdenity(state);
            }
        }

        Task.Factory.StartNew(this.startRequest, state);
    }
    catch (Exception exception)
    {
        this.HandleAsyncException(state, exception);
    }
    return source.Task;
}

что я нашел странным, так это то, что выше используется Task.Factory.StartNew для выполнения запроса при генерации TaskCompletionSource<HttpResponseMessage> и возврат Task созданные им.

почему я нахожу это странным? ну, мы много говорим о том, как связанные асинхронные операции ввода-вывода не нуждаются в дополнительных потоках за кулисами и как все это связано с перекрывающимся IO.

почему это с помощью Task.Factory.StartNew для запуска асинхронной операции ввода-вывода? это значит, что SendAsync использует не только pure асинхронный поток управления для выполнения этого метода, но вращение потока ThreadPool "за нашей спиной" выполнить свою работу.

1 ответов


this.startRequest - это делегат, который указывает на StartRequest который в свою очередь использует HttpWebRequest.BeginGetResponse для запуска асинхронного ввода-вывода. HttpClient использует асинхронный IO под обложками, просто завернутый в задачу пула потоков.

при этом обратите внимание на следующее комментарий SendAsync

// BeginGetResponse/BeginGetRequestStream have a lot of setup work to do before becoming async
// (proxy, dns, connection pooling, etc).  Run these on a separate thread.
// Do not provide a cancellation token; if this helper task could be canceled before starting then 
// nobody would complete the tcs.
Task.Factory.StartNew(startRequest, state);

это работает вокруг известной проблемы с HttpWebRequest: некоторые из его этапов обработки являются синхронными. это недостаток в этом API. HttpClient избегает блокирования перемещения что работы DNS-нить-бассейн.

это хорошо или плохо? Это хорошо, потому что это делает HttpClient неблокирующий и подходит для использования в пользовательском интерфейсе. Это плохо, потому что теперь мы используем поток для длительной работы блокировки, хотя мы ожидали, что не будем использовать потоки вообще. Это уменьшает преимущества использования асинхронного ввода-вывода.

на самом деле, это хороший пример смешения синхронизации и асинхронного ввода-вывода. Нет ничего плохого в использовании обоих. HttpClient и HttpWebRequest используют асинхронный ввод-вывод для длительная блокирующая работа (HTTP-запрос). Они используют потоки для краткосрочной работы (DNS, ...). В целом это неплохая схема. Мы избегаем большинство блокировка, и нам нужно только сделать небольшую часть кода асинхронным. Типичный компромисс 80-20. Нехорошо находить такие вещи в BCL (библиотеке), но в коде уровня приложения, который может быть очень умным компромиссом.

кажется, было бы желательно исправить HttpWebRequest. Возможно, это не так. по причинам совместимости.