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

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上而不休眠,差异和离散的凸起就消失了。

发布于 2022-06-15 00:22:48
Thread.Sleep是一种阻塞操作。您正在推动在线程池上执行大量的工作,但是每个线程都在等待250 is的时间,什么也不做。IIRC,您的线程池从基于机器内核数量的线程总数开始,但不要引用我的话。为了论证起见,如果您有一台4核心机器,那么您的池只有4个线程。您正在推动在这4个线程上执行数以百计的工作,但随后阻止它们。运行时看到这种压力越来越大,正如@Theodor所说,更多线程正在以每秒1次的速度被创建并添加到池中,这不是很多。如果你改变了
tasks.Add(Task.Run(() => Thread.Sleep(250)));至
tasks.Add(Task.Run(() => Task.Delay(250)));或者更好
tasks.Add(Task.Delay(250));你几乎肯定会看到你的问题消失。
至于为什么Task.WhenAll速度较慢,您可以查看它的来源,它做了大量的工作,它不只是检查一个简单的属性。
发布于 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线程也是繁忙的。使得每秒钟都会创建一个新线程的可能性更大。稍微改变一下行为。但是,当线程池繁忙时,计时器很可能只在一批任务完成时或在最后一批任务期间执行。
https://stackoverflow.com/questions/72623048
复制相似问题