系统定时器定时器非常不准确
本文关键字:定时器 不准确 非常 系统 | 更新日期: 2023-09-27 18:13:17
我写了一个程序,使用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秒太多了。通过对Parallel.ForEach
方法使用new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount -1 }
,这种情况不会发生,并且可以看到预期的15秒间隔。
我是否打算确保每个活动计时器总是有一个核心可用?似乎有点奇怪,因为"保留"可能是我计算的有价值的资源。
编辑:正如Yuval所指出的,通过ThreadPool.SetMinThreads
设置池中固定的最小线程数解决了这个问题。我还尝试了new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }
(因此在初始问题中没有-1
)的Parallel.ForEach
方法,这也解决了问题。然而,我没有很好的解释为什么这些修改解决了问题。也许因为创建了太多的线程,计时器线程"丢失"了很长一段时间,直到它再次被执行。
Parallel
类使用内部TPL工具称为自复制任务。它们意味着要消耗所有可用的线程资源。我不知道有什么样的限制,但它似乎是所有的消耗。几天前我已经基本上回答了同样的问题。
Parallel
类容易无限制地产生大量的任务。很容易让它产生无限的线程(每秒2个)。我认为如果没有手动指定的max-DOP, Parallel
类是不可用的。它是一个定时炸弹,在生产中会在负载下随机爆炸。
Parallel
在ASP中毒性特别大。. NET场景下,许多请求共享一个线程池。
更新:我忘记说重点了。计时器刻度在线程池中排队。如果池已饱和,它们将排队并稍后执行。(这就是为什么计时器滴答可以并发发生,也可以在计时器停止后发生。)这就解释了你所看到的。解决这个问题的方法是解决池的重载问题。
对于这种特殊场景,最好的解决方案是使用固定数量的线程自定义任务调度器。Parallel
可以使用该任务调度器。并行扩展插件有这样一个调度器。将这些工作从全局共享线程池中取出。通常,我会推荐PLINQ,但它不能使用调度程序。从某种意义上说,Parallel和PLINQ都是不必要的残缺api。
不要使用ThreadPool.SetMinThreads
。不要弄乱全局进程范围设置。
同样,不要使用Environment.ProcessorCount -1
,因为这会浪费一个核心。
计时器已经在它自己的线程中执行了
定时器是操作系统内核中的一个数据结构。没有线程,直到一个tick必须排队。不确定这究竟是如何工作的,但tick最终会排队到。net的线程池中。就是问题开始的时候。
作为一种解决方案,您可以启动一个线程,该线程在循环中休眠,以模拟计时器。但是,这是一个hack,因为它没有修复根本原因:重载的线程池。