首页 > 解决方案 > 对同时请求重新使用 HttpClient 会导致它们排队吗?

问题描述

我正在编写一个响应式 API。我们必须每秒处理 10 个请求。

问题是,发送响应需要半秒钟。所以你可以想象,服务器很快就不堪重负了。

我以异步方式处理代码,一次最多 10 个任务,以帮助缓解这种情况。

但是,我担心使用单个实例是否HttpClient是正确的方法。有人提到的建议HttpClient总是创建它的单个实例。

我有一个静态实例。虽然它是线程安全的,但至少对于PostAsync,我真的应该创建 10 个 HttpClients(或一个 HttpClients 池)以便能够更快地发送数据吗?

我假设它在半秒钟内发送出去,它不会让你发送其他'postasync'。但是我无法确认这种行为。

大多数基准和资源只关注同步发送请求,即一个接一个(即await postasync

但是对于我的用例,我需要同时发送几个,即从单独的线程。每秒回复 10 条消息(每条消息需要半秒)的唯一方法是同时发回 5 条消息——不是 5 条一条一条地排队出去,而是 5 条同时发送的消息。

我找不到任何有关如何HttpClient处理此问题的文档。我只看到了一些关于它有一个连接池的引用,但不清楚它是否实际上会同时执行多个连接,或者我是否需要创建一个包含 5 个 httpclients 的小池来轮换。

问题:HttpClient 的单个实例是否同时支持多个连接?

而且我的意思不是让您在完成之前以线程安全的方式多次调用 postasync,而是我的意思是真正打开五个同时连接并同时通过每个连接发送数据?

一个例子是,你正在向月球发送 50 个 10 字节的文件,并且有 10 秒的延迟。您的程序收集了所有 50 个文件并几乎立即对 HttpClient.PostAsync 进行了 50 次调用。

假设侦听服务可以支持它,对 HttpClient.PostAsync 的跨线程调用是否会打开 50 个连接(或其他任何限制,但超过 1 个)并发送数据,这意味着服务器会在大约 10 秒后接收所有 50 个文件?

或者它会在内部将它们排队,而你最终会等待 10x50=500 秒?

标签: c#task

解决方案


似乎没有限制,或者至少,这是一个很高的限制。

我做了一个默认的 web api 应用程序,将样板控制器方法修改为:

// GET api/values
public async Task<IEnumerable<string>> Get()
{
    Debug.Print("Called");
    Thread.Sleep(100000);
    return new string[] { "value1", "value2" };
}

然后我制作了一个程序,使用 HttpClient 的单个实例,将使用Task.Run.

List<Task> tasks = new List<Task>();

var task1 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task2 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task3 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task4 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task5 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task6 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task7 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task8 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task9 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskA = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskB = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskC = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskD = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskE = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));


await Task.WhenAll(task1, task2, task3, task4, task5, task6, task7, task8, task9, taskA, taskB, taskC, taskD);

我运行了它们,“调用”这个词被记录了 14 次。

由于 Thread.Sleep 将阻止响应,这应该意味着有 14 个同时连接。

有两个属性可能会影响最大连接数,我通过查看 google 发现:

ServicePointManager.DefaultConnectionLimit默认为 2

并且,HttpClientHandler.MaxConnectionsPerServer这也是 2。

由于我能够建立超过 2 个连接,我真的不知道它是否只是被忽略了,或者这些设置是否错误,或者是什么。更改它们似乎没有效果。

在多次停止和启动我的测试项目后,我注意到建立新连接的速度要慢得多。我猜我已经饱和了连接池。

我的结论是,如果您将这两个值设置为更高的值(以防万一,我的意思是,为什么不这样做),那么您可以同时使用单个 httpclient,其中连接将是真正并发的,而不是顺序和线程安全的。


推荐阅读