使用线程池而不是异步IO的HttpClient.SendAsync?

Yuv*_*kov 10 c# asynchronous task-parallel-library dotnet-httpclient

所以我一直在挖掘HttpClient.SendAsyncvia Reflector 的实现.我有意想知道的是这些方法的执行流程,以及确定调用哪个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;
}
Run Code Online (Sandbox Code Playgroud)

我发现奇怪的是,上面用于Task.Factory.StartNew在生成TaskCompletionSource<HttpResponseMessage>并返回Task由它创建的请求时执行请求.

为什么我觉得这很奇怪?好吧,我们继续讨论I/O绑定异步操作如何在幕后不需要额外的线程,以及它是如何重叠IO的.

为什么这Task.Factory.StartNew用于触发异步I/O操作?这意味着SendAsync不仅使用纯异步控制流来执行此方法,而且还在"我们背后"旋转ThreadPool线程来执行其工作.

usr*_*usr 13

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);
Run Code Online (Sandbox Code Playgroud)

这解决了HttpWebRequest的一个众所周知的问题:它的一些处理阶段是同步的.这是该API的一个缺陷.HttpClient通过将DNS工作移动到线程池来避免阻塞.

是好还是坏?它很好,因为它使HttpClient非阻塞并适合在UI中使用.这很糟糕,因为我们现在使用一个线程进行长时间运行的阻塞工作,尽管我们预计根本不会使用线程.这降低了使用异步IO的好处.

实际上,这是混合同步和异步IO的一个很好的例子.使用两者并没有任何内在错误.HttpClient并且HttpWebRequest正在使用异步IO进行长时间运行的阻塞工作(HTTP请求).他们使用线程进行短期工作(DNS,...).总的来说,这不是一个糟糕的模式.我们避免了大多数阻塞,我们只需要将代码的一小部分作为异步.典型的80-20权衡.在BCL(库)中找到这样的东西并不好,但在应用程序级代码中可以是非常明智的权衡.

似乎最好修复HttpWebRequest.由于兼容性原因,这可能是不可能的.