问题描述
这是一个测试方法,它启动(不等待)100 个任务,每个任务在 GetConsumingEnumerable
上调用 BlockingCollection
。 (更新:下面描述的行为并非针对此方法;它可能是任何同步阻塞的方法调用。)我想了解为什么在前 10 个任务并行启动后,后续任务按顺序开始,每个任务在下一个任务前几乎正好等待 1000 毫秒:
[TestMethod]
public void test()
{
BlockingCollection<int> list = new();
Console.WriteLine(DateTime.UtcNow + @": " + "START");
for (int i = 0; i < 100; i++)
{
int x = i;
Task.Run(() =>
{
Console.WriteLine($"{DateTime.UtcNow}: Starting {x}");
// This will just block
foreach (int item in list.GetConsumingEnumerable())
{
Console.WriteLine($"{DateTime.UtcNow}: foo {x}");
}
// We'll never get here:
Console.WriteLine($"{DateTime.UtcNow}: Finishing {x}");
});
}
Console.WriteLine(DateTime.UtcNow + @": " + "END");
// Just to give the test enough time to print all messages
Thread.Sleep(100_000);
}
这是输出的前 26 行:
02.02.2021 12:17:41: START
02.02.2021 12:17:41: END
02.02.2021 12:17:41: Starting 9
02.02.2021 12:17:41: Starting 0
02.02.2021 12:17:41: Starting 1
02.02.2021 12:17:41: Starting 6
02.02.2021 12:17:41: Starting 4
02.02.2021 12:17:41: Starting 2
02.02.2021 12:17:41: Starting 5
02.02.2021 12:17:41: Starting 7
02.02.2021 12:17:41: Starting 3
02.02.2021 12:17:41: Starting 8
02.02.2021 12:17:41: Starting 10
02.02.2021 12:17:42: Starting 11
02.02.2021 12:17:43: Starting 12
02.02.2021 12:17:44: Starting 13
02.02.2021 12:17:45: Starting 14
02.02.2021 12:17:45: Starting 15
02.02.2021 12:17:46: Starting 16
02.02.2021 12:17:47: Starting 17
02.02.2021 12:17:48: Starting 18
02.02.2021 12:17:49: Starting 19
02.02.2021 12:17:50: Starting 20
02.02.2021 12:17:51: Starting 21
02.02.2021 12:17:52: Starting 22
02.02.2021 12:17:53: Starting 23
02.02.2021 12:17:54: Starting 24
前几行正如预期的那样。但是为什么它在任务 #10 后开始等待 1000 毫秒?我的第一个假设是,由于 GetConsumingEnumerable
阻塞了线程,也许线程池中的线程在第 10 个任务之后都用完了,但这并不能解释 1000 毫秒的延迟。
解决方法
[感谢 Jon Skeet 和 Theodor Zoulias 的评论]
由于GetConsumingEnumerable
阻塞了线程,所以在第10个任务运行后,线程池中所有可用的空闲线程都被阻塞了。这与 GetConsumingEnumerable
没有任何关系:每当任务阻塞时都会发生相同的行为(例如,将调用替换为 Thread.Sleep(Timeout.Infinite)
)。
如线程池documentation中所述,扩展线程池是延迟:
作为其线程管理策略的一部分,线程池在创建线程之前会延迟。因此,当大量任务在短时间内排队时,在所有任务开始之前可能会有明显的延迟。
要确认此行为,可以使用
增加最小按需线程数ThreadPool.SetMinThreads(1000,1000);
使用此设置,所有 100 个任务都会立即运行。