我用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在池中设置一个固定的最小线程解决了这个问题。我还尝试了new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } (所以在最初的问题中没有-1 )来处理Parallel.ForEach方法,这也解决了这个问题。然而,我没有很好的解释为什么这些修改解决了这个问题。可能有太多的线程被创建,计时器线程“丢失”了很长一段时间,直到它再次被执行。
发布于 2015-09-18 22:14:56
Parallel类使用称为自复制任务的内部TPL工具。它们用于消耗所有可用的线程资源。我不知道有什么样的限制,但它似乎是全部消耗。几天前,我基本上也回答了同样的问题。
Parallel类容易在没有任何限制的情况下产生大量的任务。它很容易激发它字面上产生无限的线程(2每秒)。如果没有手动指定的max-DOP,我认为Parallel类不可用。它是一种在负荷下随机爆炸的定时炸弹。
在许多请求共享一个线程池的ASP.NET场景中,ASP.NET特别有害。
更新:--我忘记指出要点了。计时器滴答被排队到线程池。如果池是饱和的,它们将在稍后的时间排队并执行。(这就是为什么定时器滴答可以同时发生或在定时器停止之后发生的原因。)这解释了你所看到的。修复该问题的方法是修复池重载。
这个特定场景的最佳修复方法是一个具有固定线程数量的自定义任务调度程序。可以使Parallel使用该任务调度程序。并行扩展Extras具有这样的调度程序。将该工作从全局共享线程池中删除。通常情况下,我会重新评论PLINQ,但这不能接受调度程序。从某种意义上说,并行和PLINQ都是不必要的瘫痪API。
不要使用ThreadPool.SetMinThreads。不要破坏全局进程范围的设置。别管可怜的线程池了。
另外,不要使用Environment.ProcessorCount -1,因为这浪费了一个核心。
定时器已经在自己的线程中执行了。
定时器是操作系统内核中的数据结构。在必须排队之前,没有线程。不确定它到底是如何工作的,但滴答最终被排队到.NET中的线程池中。问题就是从那时开始的。
作为一种解决方案,您可以启动一个在循环中休眠的线程,以模拟计时器。不过,这是一次黑客攻击,因为它没有解决根本原因:重载的线程池。
https://stackoverflow.com/questions/32659147
复制相似问题