簡體   English   中英

使用線程池而不是異步IO的HttpClient.SendAsync?

[英]HttpClient.SendAsync using the thread-pool instead of async IO?

所以我一直在通過Reflector挖掘HttpClient.SendAsync的實現。 我有意想知道的是這些方法的執行流程,以及確定調用哪個API來執行異步IO工作。

在探索了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執行請求。

為什么我覺得這很奇怪? 好吧,我們繼續討論I / O綁定異步操作如何在幕后不需要額外的線程,以及它是如何重疊IO的。

為什么使用Task.Factory.StartNew來觸發異步I / O操作? 這意味着SendAsync不僅使用純異步控制流來執行此方法,而且還在“我們背后”旋轉ThreadPool線程來執行其工作。

this.startRequest是指向一個代表到StartRequest又使用HttpWebRequest.BeginGetResponse開始異步IO。 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非阻塞並適合在UI中使用。 這很糟糕,因為我們現在使用一個線程進行長時間運行的阻塞工作,盡管我們預計根本不會使用線程。 這降低了使用異步IO的好處。

實際上,這是混合同步和異步IO的一個很好的例子。 使用兩者並沒有任何內在錯誤。 HttpClientHttpWebRequest使用異步IO進行長時間運行的阻塞工作(HTTP請求)。 他們使用線程進行短期工作(DNS,...)。 總的來說,這不是一個糟糕的模式。 我們避免了大多數阻塞,我們只需要將代碼的一小部分作為異步。 典型的80-20權衡。 在BCL(庫)中找到這樣的東西並不好,但在應用程序級代碼中可以是非常明智的權衡。

似乎最好修復HttpWebRequest 由於兼容性原因,這可能是不可能的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM