c# - HttpClient.SendAsync 使用线程池而不是异步 IO?

标签 c# asynchronous task-parallel-library dotnet-httpclient

所以我一直在深入研究 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 绑定(bind)异步操作如何在幕后不需要额外的线程,以及它是如何与重叠 IO 相关的。

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

最佳答案

this.startRequest 是一个指向 StartRequest 的委托(delegate),后者又使用 HttpWebRequest.BeginGetResponse 来启动异步 IO。 HttpClient 在幕后使用异步 IO,只是包装在线程池任务中。

也就是说,请注意以下内容 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);

This works around a well-known problem with HttpWebRequest: Some of its processing stages are synchronous.这是该 API 中的一个缺陷。 HttpClient 通过将 DNS 工作移至线程池来避免阻塞。

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

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

看来最好修复 HttpWebRequest。出于兼容性原因,这可能是不可能的。

关于c# - HttpClient.SendAsync 使用线程池而不是异步 IO?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24983635/

相关文章:

C# 在 X 项后跳出 foreach 循环

c# - 类似于 Watin 的 Silverlight 自动化

c# - C# 是否支持按结果调用?

c# - MVVM:异步事件处理程序的单元测试

.Net: CallBack 应该在哪个线程?

c# - 跟踪 c#/.NET 任务流

c# - 我的套接字可以使用哪个端口?

python - 协程 yield 与任务 yield

c#-4.0 - 限制正在进行的流式资源并行操作的工作

C# Tasks - 为什么在这种情况下需要 noop 行