启动多个阻塞任务的奇怪行为

问题描述

这是一个测试方法,它启动(不等待)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 个任务都会立即运行。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...