Thread.Sleep导致Timer的回调延迟

时间:2013-11-01 08:29:41

标签: .net multithreading

这里我的测试代码中有一个奇怪的行为,代码非常简单,创建了一堆具有相同模式的Timers:在所有回调中添加 Thread.Sleep 。然后几乎同时启动计时器,然后我可以看到一些计时器的回调是延迟

public class StrangTimerTesting
{
    public int callback_EnteredTimes;

    // for avoid timers get GCed.
    private List<System.Timers.Timer> timerContainer = new List<System.Timers.Timer>();

    public void Go()
    {
        for (var i = 0; i < 5; i++)
        {
            var displayTimer = new System.Timers.Timer(1000);
            displayTimer.Elapsed += (a, b) =>
            {
                Interlocked.Increment(ref this.callback_EnteredTimes);
                var initalTime = DateTime.Now.ToString("HH:mm:ss.ffff");
                Console.WriteLine("entered times: " + callback_EnteredTimes + ", intial@" + initalTime);
                displayTimer.Stop();

                // why this Sleep cause some callback delayed to be called????
                Thread.Sleep(6000);
            };

            displayTimer.Start();
            timerContainer.Add(displayTimer);
        }
    }
}

我想虽然在不同的线程池线程上几乎同时调用了所有的回调,但测试结果显然不支持这个,有一些 2秒的差距,如果我删除了Thread.Sleep,然后一切都很好。有人可以指出原因吗?

EDIT1:这是测试程序的结果:

*输入次数:1,intial @ 08:43:29.4732

进入时间:2,初始@ 08:43:29.4762

进入时间:3,intial @ 08:43:30.4763

输入次数:4,intial @ 08:43:30.9764

输入次数:5,intial @ 08:43:31.4764 *

我的笔记本电脑中的MinThreads数量是2。

2 个答案:

答案 0 :(得分:7)

您创建了 firehose问题。你有5个计时器,每个计时器在1秒钟内滴答,其Elapsed事件处理程序休眠6秒。您希望您的程序实际上每秒添加5个线程,需要30秒才能完成工作。换句话说,它每分钟增加300个线程并只完成其中的10个。

如果不加以控制,这将无法达到目的。说得客气一点。线程是一种非常昂贵的操作系统资源。超过5个句柄,它消耗一兆字节的虚拟内存。在32位进程中,只需7分钟即可占用所有可用内存,并在OutOfMemoryException时崩溃。

.NET不会让你这么做,不是没有打架。它使用的对策是您观察到的,它让您启动那么多线程。它故意减慢允许启动的新速率。它只会在你的机器上每秒允许2个新线程。

这是ThreadPool调度程序的工作。它尝试将执行TP线程的数量保持在设定的最小值。在您的计算机上,最小值为2,即您拥有的核心数。让它运行超过2并没有多大意义,操作系统将不得不做更多的工作来为这些线程提供运行机会,在它们之间进行上下文切换。实际上减慢它们的速度,完美的数字是2,所以他们很有可能无限制地访问处理器并尽快完成工作。

然而,ThreadPool调度程序不知道线程正在做什么,它对它们正在执行的代码有非常不完全的了解。它不知道你的线程实际上根本没有完成任何工作而只是在睡觉。它有一个对策。如果活动线程没有完成,它会覆盖其默认调度策略,它允许额外线程启动。如果绝对必要,这将继续,直到允许的设定最大值。一个非常高的数字,你的机器应该是500左右。

这是一个非常有效的算法,它会阻止您的程序在7分钟后爆炸。它仍然会爆炸,但这需要非常长的时间。你最终仍然会让OOM拥有TP调度程序无法提供服务的数百万个TP线程启动请求。否则只是一个不合理的结果,当你写一个不合理的程序时,你总是需要它。

答案 1 :(得分:1)

来自ThreadPool docs on MSDN

  

当达到最小值时,线程池可以创建其他线程或等到某些任务完成。

我怀疑您的线程池最小值默认为2,因为您的系统有2个核心。该框架将向线程池ID添加线程,但是所有池线程当前都很忙。但是,正如在文档中提到的那样,框架可能会等待(并且显然正在发生)推测正在使用的工作线程很快就会可用。

您可以使用ThreadPool.GetMinThreads()方法输出最小线程池数。