如何防止 System.Timers.Timer 在线程池上排队执行?
Posted
技术标签:
【中文标题】如何防止 System.Timers.Timer 在线程池上排队执行?【英文标题】:How to prevent System.Timers.Timer from queuing for execution on a thread pool? 【发布时间】:2011-06-20 01:41:59 【问题描述】:标准 System.Timers.Timer 行为存在问题。计时器以一定的时间间隔引发 Elapsed 事件。但是当 Elapsed 事件处理程序内的执行时间超过计时器间隔时,线程池开始排队事件处理。在我的情况下这是一个问题。这是因为我使用 Elapsed 事件处理程序从数据库中获取一些数据并对其进行处理,最后将结果保存回数据库。但是数据处理应该只提供一次。那么,有没有办法防止对 System.Timers.Timer 的 elapse 事件进行排队。
作为这个问题的说明,你可以考虑下一个测试程序:
public class EntryPoint
private static void TimeProc(object state, ElapsedEventArgs e)
Console.WriteLine("Current time 0 on the thread 1", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(20000);
static void Main(string[] args)
Console.WriteLine("Press <Enter> for finishing\n\n");
ThreadPool.SetMaxThreads(10, 10);
System.Timers.Timer MyTimer = new System.Timers.Timer(1000);
MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
MyTimer.Start();
Console.ReadLine();
MyTimer.Stop();
可能的输出如下:
Current time 03.02.2011 0:00:09 on the thread 4
Current time 03.02.2011 0:00:10 on the thread 5
Current time 03.02.2011 0:00:12 on the thread 6
Current time 03.02.2011 0:00:13 on the thread 7
Current time 03.02.2011 0:00:14 on the thread 8
Current time 03.02.2011 0:00:15 on the thread 9
Current time 03.02.2011 0:00:16 on the thread 10
Current time 03.02.2011 0:00:17 on the thread 11
Current time 03.02.2011 0:00:18 on the thread 12
Current time 03.02.2011 0:00:19 on the thread 13
Current time 03.02.2011 0:00:30 on the thread 4
Current time 03.02.2011 0:00:30 on the thread 5
可能的解决方案:
1) 灵感来自:C# Timer vs Thread in Service
关于上述示例,这里有一个类似的代码:
public class EntryPoint
private static System.Timers.Timer MyTimer;
private static void TimeProc(object state, ElapsedEventArgs e)
Console.WriteLine("Current time 0 on the thread 1", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(20000);
MyTimer.Enabled = true;
static void Main(string[] args)
Console.WriteLine("Press <Enter> for finishing\n\n");
ThreadPool.SetMaxThreads(10, 10);
MyTimer = new System.Timers.Timer(1000);
MyTimer.AutoReset = false;
MyTimer.Elapsed += new ElapsedEventHandler(TimeProc);
MyTimer.Enabled = true;
Console.ReadLine();
2) 第二种方式是关于 SynchronizingObject,但它仅对 Windows 窗体应用程序有价值或需要额外开发代码来实现将实现 ISynchronizeInvoke 接口的对象。有关此方式的更多信息,您可以找到here
所以,现在我更喜欢第一个解决方案。
【问题讨论】:
计时器没有问题...这种情况到处都有。将 Elapsed 事件排队实际上很好。如果这是一个硬件中断,你会破坏你的间隔并且你无法恢复。 如果数据应该只处理一次,那么为什么要重复计时器,或者您的意思是每 X 次最多一次? @Chris 在我的情况下,另一个应用程序将一些数据保存到数据库中,我的应用程序应该读取并处理它。 【参考方案1】:在这种情况下,我通常会在 Elapsed
处理程序的开始处停止计时器并在结束时重新启动它。这样,您一次只处理一个刻度。
更新:
根据 MSDN 链接,我认为他们的意思是您可以设置自己的标志(但仍然有刻度),但也应采取线程安全措施。
【讨论】:
是的,你是对的。但这似乎不是一个正确的方法,因为它需要额外的资源来停止和启动计时器。 @apros:您遇到的行为是设计使然。如果您将计时器设置为每 30 秒滴答一分钟,您会期望触发两个滴答事件,对吗?如果您希望它的行为有所不同,这就是这样做的方法。 MSDN 在此处声明:msdn.microsoft.com/en-us/library/…“解决此竞争条件的一种方法是设置一个标志,告诉 Elapsed 事件的事件处理程序忽略后续事件。”。所以,我希望应该有另一种方法来解决这个问题。 如果您采用这种方法,您希望在 finally 块中重新启动计时器。否则,您可能会在处理时遇到异常,并且计时器将永远不会重新启动。由于这个和其他一些原因,我认为这不是一个好方法。 这个 sn-p 有很大的缺陷,它不是线程安全的。需要互锁。查看 Timer.Stop() 方法文档中的 MSDN 示例代码。【参考方案2】:我想说简单地停止它,然后在像这样长时间执行后启动它。
tmr.Stop();
//Your lengthy execution code goes here
tmr.Start();
【讨论】:
这通常是我所做的,但是您在使用 stop 时也应该注意这一点:msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx 开头的黄色部分警告用户在停止时不要吃第二顿午餐。 @Chris 如果你在谈论他的情况下没有线程但我没多久我在我的服务中与一个 tomer 一起工作,我必须能够捕获其他事件,同时让我的执行代码完成.这就是我开始谈论它的原因。 @Shekhar_Pro 如果您知道这一点,您可以在停止计时器之前从委托中删除处理程序吗? 我知道..但从未尝试过..似乎是个好主意...我会检查一下..谢谢 这个不行,两个Elapsed call可以同时挂起。【参考方案3】:您看到的行为是设计使然。要么在计时器上设置SynchronizingObject,要么使用另一个不会在多个线程上计时的计时器(例如System.Threading.Timer)。
【讨论】:
您提供的有关 System.Threading.Timer 的信息不正确。见msdn.microsoft.com/en-us/library/system.threading.timer.aspx - "定时器执行的回调方法应该是可重入的,因为它是在ThreadPool线程上调用的。如果定时器间隔小于执行回调所需的时间,回调可以同时在两个线程池线程上执行,或者如果所有线程池线程都在使用并且回调被多次排队。”也许您正在考虑 System.Windows.Forms.Timer 另外,设置一个同步对象(如果我的理解是正确的)不会阻止排队,它只会限制它在队列外一次处理一个。请参阅 msdn.microsoft.com/en-us/magazine/cc164015.aspx 了解有关行为的一些详细信息的计时器比较。【参考方案4】:我只是创建一个静态标志变量。这样我的计时器继续运行,但如果该方法在下一个计时器周期之前尚未完成,则代码将被简单地绕过。
在用于计时器的方法中,测试事件是否正在进行中。
Timer_Method_Called()
if (eventInProgress == 0)
// flag event as in progress
eventInProcess == 1;
// perform code....
// after code is complete, allow the method to execute
eventInProgress == 0;
【讨论】:
这不是线程安全的,您使用 == 来完成我假设您的任务 (=)。【参考方案5】:由于没有一个答案是线程安全的,所以让我提出一个:
void oneHundredMS_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
if (setTimerBodyRunning()) //only proceed to body if it is not already processing; setTimerBodyRunning must be thread-safe
// here you do your long running operation
setTimerBodyFinished();
如您所见,计时器处理程序首先检查它是否尚未运行,如果返回 false,则仅继续执行主体。如果返回 true,则处理程序会快速返回,并且滴答声不会排队(他们本来可以使用简单的 lock 语句)。下面是 setTimerBodyRunning 和 setTimerBodyFinished 的定义:
private bool setTimerBodyRunning()
bool retVal = false;
lock (timerBodyRunning) //timerBodyRunning is type object and it holds a bool.
//The reason it is object and not bool is so it can be locked on to ensure thread safety
if (!((bool)timerBodyRunning))
timerBodyRunning = true;
retVal = true;
return retVal;
private void setTimerBodyFinished()
lock (timerBodyRunning)
timerBodyRunning = false;
以下是初始化和启动计时器的方法:
object timerBodyRunning = new object();
timerBodyRunning = false;
System.Timers.Timer timerFrequency100MS = new System.Timers.Timer();
timerFrequency100MS.Interval = FREQUENCY_MS; //it will fire every 100 milliseconds
timerFrequency100MS.Elapsed += new System.Timers.ElapsedEventHandler(oneHundredMS_Elapsed);
timerFrequency100MS.Start();
【讨论】:
这解决了线程安全的问题。考虑将 setTimerBodyFinished 调用放在 finally 块中,以确保它总是被调用。以上是关于如何防止 System.Timers.Timer 在线程池上排队执行?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 ElapsedEvent 期间保持 System.Timers.Timer 停止
System.Timers.Timer 与 System.Threading.Timer
为啥 System.Timers.Timer 能在 GC 中存活,而 System.Threading.Timer 不能?