首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WhenAll是否增加了不必要的延迟?此外,是什么导致了执行时的离散尖峰(C#/.NET)

WhenAll是否增加了不必要的延迟?此外,是什么导致了执行时的离散尖峰(C#/.NET)
EN

Stack Overflow用户
提问于 2022-06-14 20:32:20
回答 2查看 100关注 0票数 1

在等待任务完成时,是什么导致了执行时间上的这些离散尖峰?为什么使用WhenAll比快速循环和检查所有任务是否都完成要慢呢?这是一个简化的示例,但创建是因为我们在调用WhenAll时似乎看到了不必要的延迟。NativeAOT似乎没有那么受影响,所以可能是一些不必要的JITing?

代码语言:javascript
复制
using System.Diagnostics;
namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main()
        {
            for (int i = 1; i <= 100; i+=1) 
            {
                await RunTest(i);
            }
        }

        public static async Task RunTest(int count) 
        {
            var sw = Stopwatch.StartNew();
            var tasks = new List<Task>();

            // Construct started tasks
            for (int i = 0; i < count; i++)
            {
                tasks.Add(Task.Run(() => Thread.Sleep(250)));
            }

            // Test 1, WhenAll
            //await Task.WhenAll(tasks);

            // Test 2, 10ms loop
            bool completed = false;
            while (!completed)
            {
                await Task.Delay(10);
                completed = tasks.All(t => t.IsCompleted);
            }

            Console.WriteLine($"{count},{sw.Elapsed.TotalSeconds}");
        }
    }
}

以上所有数据都是从命令行运行自包含的exe中收集的。但是当在VS中运行时,它似乎不是垃圾收集问题,我怀疑这一点,因为VS诊断没有显示任何垃圾收集标记。

编辑:一旦将真正的负载放在CPU上而不休眠,差异和离散的凸起就消失了。

EN

回答 2

Stack Overflow用户

发布于 2022-06-15 00:22:48

Thread.Sleep是一种阻塞操作。您正在推动在线程池上执行大量的工作,但是每个线程都在等待250 is的时间,什么也不做。IIRC,您的线程池从基于机器内核数量的线程总数开始,但不要引用我的话。为了论证起见,如果您有一台4核心机器,那么您的池只有4个线程。您正在推动在这4个线程上执行数以百计的工作,但随后阻止它们。运行时看到这种压力越来越大,正如@Theodor所说,更多线程正在以每秒1次的速度被创建并添加到池中,这不是很多。如果你改变了

代码语言:javascript
复制
tasks.Add(Task.Run(() => Thread.Sleep(250)));

代码语言:javascript
复制
tasks.Add(Task.Run(() => Task.Delay(250)));

或者更好

代码语言:javascript
复制
tasks.Add(Task.Delay(250));

你几乎肯定会看到你的问题消失。

至于为什么Task.WhenAll速度较慢,您可以查看它的来源,它做了大量的工作,它不只是检查一个简单的属性。

票数 3
EN

Stack Overflow用户

发布于 2022-06-15 02:21:09

其他答案包括线程池饥饿。但是为什么Task.Delay / Task.WhenAll图看起来不同呢?

每次调用RunTest时,都会添加另一个任务。如果所有任务都可以并行运行,RunTest将在250 If内返回。如果不能执行,则计划的任务将分批执行,RunTest将以n * 250ms格式返回。因为正如其他人所指出的,线程池每秒钟只添加一个新线程,所以当n ~= 4时,您会期望结果稳定下来。

当任务完成时,任何延续都将立即在同一线程上运行。Task.WhenAll为每个任务添加一个延续,在每个任务完成时减少一个计数器。在最后一个任务上,它将收集结果并完成它自己的任务。所以它不会给线程池增加任何压力。

但是,每10 to运行一次额外的任务确实会给线程池增加压力。这个额外的任务将确保线程池被认为是繁忙的,即使没有其他的k-1线程也是繁忙的。使得每秒钟都会创建一个新线程的可能性更大。稍微改变一下行为。但是,当线程池繁忙时,计时器很可能只在一批任务完成时或在最后一批任务期间执行。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72623048

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档