C# · 12月 29, 2021

c# – System.Timers.Timer大大不准确

我编写了一个使用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方法使用新的ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount -1},这不会发生,并且可以看到预期的15秒间隔.

是否打算我必须确保每个活动定时器总是有一个核心可用?似乎有点奇怪,因为“保留”可能是我的计算的宝贵资源.

编辑:如Yuval所指出的,通过ThreadPool.SetMinThreads在池中设置固定的最小线程来解决问题.对于Parallel.ForEach方法,我也尝试过新的ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}(所以在初始问题中没有-1),这也解决了这个问题.然而,我没有很好的解释为什么这些修改解决了这个问题.也许有这么多的线程创建,定时器线程被“丢失”一个“长”时间,直到再次执行.

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

并行类容易产生疯狂数量的任务,没有任何限制.很容易挑起它来逐字地产生无限线程(每秒2次).我认为并行类不可用,没有手动指定的max-DOP.它是一个定时炸弹,在生产中随机地在负载下爆炸.

在许多请求共享一个线程池的ASP.NET场景中,并行特别有毒.

更新:我忘了提出要点.定时器刻度排队到线程池.如果池饱和,它们就会排队,稍后执行. (这就是为什么定时器打勾同时发生或者在定时器停止之后).这就解释了你所看到的.解决方法是修复池重载.

对于特定场景的最佳修复将是具有固定线程数的自定义任务调度程序.可以并行使用该任务调度程序.并行扩展附件有这样一个调度程序.将该工作从全局共享线程池中完成.通常我会推荐PLINQ,但这是不能采取一个调度程序.在某种意义上,Parallel和PLINQ都是不必要的削弱API.

不要使用ThreadPool.SetMinThreads.不要混淆全局流程广泛的设置.只要离开糟糕的线程池.

另外,不要使用Environment.ProcessorCount -1,因为浪费了一个核心.

the timer is already executed in its own thread

定时器是OS内核中的数据结构.直到一个勾号必须排队才有线程.不知道这是如何工作的,但是tick最终排在.NET的线程池中.那就是问题开始的时候.

作为一个解决方案,您可以启动在循环中睡眠的线程,以模拟定时器.这是一个黑客,但是,因为它不能解决根本原因:重载的线程池.