基于任务的并发比直接使用 System.Threading.Thread 慢得多

Posted

技术标签:

【中文标题】基于任务的并发比直接使用 System.Threading.Thread 慢得多【英文标题】:Task based concurrency much slower than when directly using System.Threading.Thread 【发布时间】:2015-06-25 14:08:00 【问题描述】:

我一直在使用 System.Threading.Task 和 System.Net.Http.HttpClient 对 Web 服务器进行负载测试,并观察到一些奇怪的行为。代码如下:

var taskArray = new List<Task>();              
for (int i = 0; i < 105; i++)
    taskArray.Add(Task.Factory.StartNew(() => Get("/content/test.jpg")));

Task.WaitAll(taskArray.ToArray());

即使每个请求(通过提琴手监控时)只需要大约 15 毫秒的时间来执行,我还是收到了由用于发出请求的 HttpClient 引发的超时异常(嗯,TaskCanceledExcpetions - 这是同一件事)。请求时间超过了 100 秒的默认超时时间。

我尝试的第一件事是增加 HttpClient 的超时,这很有效,但我仍然难以理解为什么响应时间很短的请求会超时。因此,我在调用 HttpClient.PostAsync 之前设置了一个计时器,并检查了完成所需的时间,正如怀疑的那样,时间超过了 100 秒,尽管服务器发送响应的速度要快得多。

然后我读到 HttpClient.Timeout 是整个异步操作的超时时间,这让我认为可能是任务调度程序导致我出现问题,因为它仅在响应准备好后执行接收响应的异步回调很长时间收到。

考虑到这一点,我决定使用旧的 System.Threading.Thread 编写代码:

var handle = new EventWaitHandle(false, EventResetMode.ManualReset);
for (int i = 0; i < 105; i++)

    var t = new Thread(() =>
    
        Get("/content/test.jpg");
        if (Interlocked.Decrement(ref numberOfTasks) == 0)
            handle.Set();
    );
    t.Start();


handle.WaitOne();

这按预期工作!我什至可以将线程数增加到 2000,它完成所有线程的速度比基于任务的版本发送 105 更快!

什么给了?

【问题讨论】:

发布获取代码。您是否增加了 .NET HTTP 限制? 获取代码是:httpClient.GetAsync(url).Result 既然我已经看过这个,也许我应该尝试等待该操作,而不是阻塞结果....应该有发现了!是的,我确实尝试通过 ServicePointManager 提高限制,但没有帮助。 【参考方案1】:

正如 Matthew 所说,这是因为 Task 工厂默认使用线程池。如果您想使用任务并想提高线程池限制,稍微提高限制或创建自己的线程池可能对您有一些价值。

也就是说,您已经在使用具有所有异步方法的类:您为什么要尝试将它们包装在线程中?只需捕获任务并等待它们。

类似这样的:

var tasks = new List<Task>();              
for (int i = 0; i < 105; i++)
    tasks.Add(client.GetAsync(uri));

Task.WaitAll(tasks.ToArray());

【讨论】:

值得注意的是,并行发送 1000 个请求可能不是一个好主意。使用其中一种并行异步 foreach 习惯用法可能会更好。但这很可能是正确的答案。 我将它们包装在线程中,因为我需要请求尽可能并发,异步,而后续请求将在收到对先前请求的响应之前发送,并行度将低于螺纹。 @usr 这不是生产场景,而是负载测试场景。我尝试并行发送垃圾邮件请求的原因是我们在生产中看到了这种类型的使用模式偶尔会导致异常的问题,因此我正在尝试为这种模式获取一些测试覆盖率。正是请求几乎在同一时间发生的事实才导致问题出现。 @SamShiles 好的,异步 IO 允许您发送 更多 个并行请求,而不是更少。它需要更少的资源。线程需要一段时间才能启动,而异步 IO 启动速度非常快。在 10k 线程时,Windows 调度程序开始表现不稳定,我不会接近这个数量。 有趣,我会玩一玩并报告我的发现!非常感谢您的意见。

以上是关于基于任务的并发比直接使用 System.Threading.Thread 慢得多的主要内容,如果未能解决你的问题,请参考以下文章

Azure SDK for .NET 中基于任务的 API 中的并发性

Java核心技术-并发

Java基于线程池的独立任务并发执行器

Java基于线程池的独立任务并发执行器

Java基于线程池的独立任务并发执行器

Golang语言社区投稿golang高并发基于协程,通道的任务池