System.Timers.Timer 严重不准确

Posted

技术标签:

【中文标题】System.Timers.Timer 严重不准确【英文标题】:System.Timers.Timer massively inaccurate 【发布时间】:2015-12-16 00:34:18 【问题描述】:

我通过Parallel.ForEach 编写了一个使用所有可用内核的程序。 ForEach 的列表包含约 1000 个对象,每个对象的计算需要一些时间(约 10 秒)。 在这种情况下,我设置了一个这样的计时器:

timer = new System.Timers.Timer();
timer.Elapsed += TimerHandler;
timer.Interval = 15000;
timer.Enabled = true;

private void TimerHandler(object source, ElapsedEventArgs e)

        Console.WriteLine(DateTime.Now + ": Timer fired");

目前TimerHandler 方法是一个存根,以确保问题不是由该方法引起的。

我的期望是TimerHandler 方法将每 15 秒执行一次。但是,两次调用此方法之间的时间甚至达到了 40 秒,所以 25 秒太多了。 通过将new ParallelOptions MaxDegreeOfParallelism = Environment.ProcessorCount -1 用于Parallel.ForEach 方法,这不会发生,并且会看到预期的15 秒间隔。

我是否必须确保每个活动计时器始终有一个内核可用?似乎更奇怪了,因为“保留”可能是我计算的宝贵资源。

编辑:正如 Yuval 所指出的那样,ThreadPool.SetMinThreads 在池中设置了固定的最小线程数解决了这个问题。我还为Parallel.ForEach 方法尝试了new ParallelOptions MaxDegreeOfParallelism = Environment.ProcessorCount (所以在最初的问题中没有-1),这也解决了这个问题。但是,我没有很好的解释为什么这些修改解决了这个问题。也许创建了太多线程,以至于计时器线程“丢失”了“长时间”,直到再次执行。

【问题讨论】:

如果在执行并行循环之前明确设置线程池中的线程数,还会出现这种情况吗? (使用ThreadPool.SetMinThreads 当您使用所有处理器时,您是否在跟踪处理器使用情况?如果是这样,由于 windows 处理消息泵的方式,所有事件的响应速度都会变慢。 我怀疑运行并行线程的线程(可能是运行计时器的同一线程)正在阻塞自己。如果您能找到一种在不同线程上运行计时器的方法,您的问题可能会消失。运行计时器的线程可能会有所不同,具体取决于您的应用的托管方式(例如 Windows 窗体与控制台应用) @YuvalItzchakov 这似乎有效,谢谢。我已将其设置为固定值(即使我只有 4 个内核也尝试了 50)并且它可以工作。我也试过设置new ParallelOptions MaxDegreeOfParallelism = Environment.ProcessorCount (所以没有-1),效果一样。 问题已解决 - 请解释一下?为什么设置这个可以解决问题,而不管值设置为什么? 【参考方案1】:

Parallel 类使用称为自我复制任务的内部 TPL 工具。它们旨在消耗所有可用的线程资源。我不知道有什么样的限制,但它似乎很费力。几天前我已经回答了基本相同的问题。

Parallel 类容易产生大量无限制的任务。很容易激起它从字面上产生无限线程(每秒 2 个)。我认为如果没有手动指定的 max-DOP,Parallel 类将无法使用。这是一个定时炸弹,在负载下在生产中随机爆炸。

Parallel 在许多请求共享一个线程池的 ASP.NET 场景中尤其有害。

更新:我忘了说重点。计时器滴答在线程池中排队。如果池已饱和,它们将排队并在稍后执行。 (这就是为什么计时器滴答可以同时发生或在计时器停止之后发生的原因。)这解释了您所看到的。解决这个问题的方法是修复池过载。

对于这种特殊情况,最好的解决办法是使用具有固定线程数的自定义任务调度程序。可以使Parallel 使用该任务调度程序。 Parallel Extension Extras 有这样的调度程序。从全局共享线程池中完成这项工作。通常,我会推荐 PLINQ,但它不能使用调度程序。从某种意义上说,Parallel 和 PLINQ 都是不必要的残缺 API。

不要使用ThreadPool.SetMinThreads。不要乱用全局进程范围的设置。就别管可怜的线程池了。

另外,不要使用Environment.ProcessorCount -1,因为这会浪费一个内核。

定时器已经在自己的线程中执行了

定时器是操作系统内核中的一种数据结构。在必须排队一个滴答之前没有线程。不确定它到底是如何工作的,但滴答声最终会排队到 .NET 中的线程池中。 那是问题开始的时候。

作为一种解决方案,您可以启动一个在循环中休眠的线程以模拟计时器。不过,这是一个 hack,因为它不能解决根本原因:线程池过载。

【讨论】:

以上是关于System.Timers.Timer 严重不准确的主要内容,如果未能解决你的问题,请参考以下文章

System.Timers.Timer 与 System.Threading.Timer

为啥 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?

使用System.Timers.Timer类实现程序定时执行

System.Timers.Timer

System.Windows.Forms.TimerSystem.Timers.TimerSystem.Threading.Timer

System.Timers.Timer 几毫秒后第二次触发