HttpClient.SendAsync 使用线程池而不是异步 IO? [英] HttpClient.SendAsync using the thread-pool instead of async IO?

查看:32
本文介绍了HttpClient.SendAsync 使用线程池而不是异步 IO?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我一直在通过 Reflector 研究 HttpClient.SendAsync 的实现.我特意想了解的是这些方法的执行流程,并确定调用哪个 API 来执行异步 IO 工作.

So I've been digging up on the implementation of HttpClient.SendAsync via Reflector. What I intentionally wanted to find out was the flow of execution of these methods, and to determine which API gets called to execute the asynchronous IO work.

在探索了 HttpClient 中的各种类之后,我看到它在内部使用了 HttpClientHandler,它派生自 HttpMessageHandler 并实现了它的 SendAsync 方法.

After exploring the various classes inside HttpClient, I saw that internally it uses HttpClientHandler which derives from HttpMessageHandler and implements its SendAsync method.

这是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 并返回 Task 由它创建.

What I found weird is that the above uses Task.Factory.StartNew to execute the request while generating a TaskCompletionSource<HttpResponseMessage> and returning the Task created by it.

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

Why do I find this weird? well, we go on alot about how I/O bound async operations have no need for extra threads behind the scenes, and how its all about overlapped IO.

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

Why is this using Task.Factory.StartNew to fire an async I/O operation? this means that SendAsync isn't only using pure async control flow to execute this method, but spinning a ThreadPool thread "behind our back" to execute its work.

推荐答案

this.startRequest 是指向 StartRequest 的委托,后者又使用 HttpWebRequest.BeginGetResponse 以启动异步 IO.HttpClient 在幕后使用异步 IO,只是包裹在线程池任务中.

this.startRequest is a delegate that points to StartRequest which in turn uses HttpWebRequest.BeginGetResponse to start async IO. HttpClient is using async IO under the covers, just wrapped in a thread-pool Task.

也就是说,请注意以下 SendAsync 中的注释

That said, note the following comment in 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 工作移至线程池来避免阻塞.

This works around a well-known problem with HttpWebRequest: Some of its processing stages are synchronous. That is a flaw in that API. HttpClient is avoiding blocking by moving that DNS work to the thread-pool.

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

Is that good or bad? It is good because it makes HttpClient non-blocking and suitable for use in a UI. It is bad because we are now using a thread for long-running blocking work although we expected to not use threads at all. This reduces the benefits of using async IO.

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

Actually, this is a nice example of mixing sync and async IO. There is nothing inherently wrong with using both. HttpClient and HttpWebRequest are using async IO for long-running blocking work (the HTTP request). They are using threads for short-running work (DNS, ...). That's not a bad pattern in general. We are avoiding most blocking and we only have to make a small part of the code async. A typical 80-20 trade-off. It is not good to find such things in the BCL (a library) but in application level code that can be a very smart trade-off.

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

It seems it would have been preferable to fix HttpWebRequest. Maybe that is not possible for compatibility reasons.

这篇关于HttpClient.SendAsync 使用线程池而不是异步 IO?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆