同步计时器以防止重叠

我正在编写一个Windows服务,每隔一段时间运行一次可变长度的活动(数据库扫描和更新)。 我需要经常运行此任务,但要处理的代码并不是可以安全地同时运行多次。

我怎样才能最简单地设置一个计时器来每30秒运行一次任务,而不会重复执行? (我假设System.Threading.Timer是这项工作的正确计时器,但可能是错误的)。

您可以使用Timer执行此操作,但您需要在数据库扫描和更新时使用某种forms的锁定。 简单的同步lock可能足以防止发生多次运行。

话虽如此,在操作完成后启动计时器可能会更好,只需使用一次,然后停止它。 在下一次操作后重新启动它。 这会在事件之间给你30秒(或N秒),没有重叠的可能性,也没有锁定。

示例:

 System.Threading.Timer timer = null; timer = new System.Threading.Timer((g) => { Console.WriteLine(1); //do whatever timer.Change(5000, Timeout.Infinite); }, null, 0, Timeout.Infinite); 

立即工作…..完成……等待5秒….立即工作…..完成……等待5秒….

我在你已用的代码中使用了Monitor.TryEnter:

 if (Monitor.TryEnter(lockobj)) { try { // we got the lock, do your work } finally { Monitor.Exit(lockobj); } } else { // another elapsed has the lock } 

我更喜欢System.Threading.Timer的事情,因为我不必经历事件处理机制:

 Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000); object updateLock = new object(); void UpdateCallback(object state) { if (Monitor.TryEnter(updateLock)) { try { // do stuff here } finally { Monitor.Exit(updateLock); } } else { // previous timer tick took too long. // so do nothing this time through. } } 

您可以通过使计时器一次性完成并在每次更新后重新启动它来消除锁定的需要:

 // Initialize timer as a one-shot Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite); void UpdateCallback(object state) { // do stuff here // re-enable the timer UpdateTimer.Change(30000, Timeout.Infinite); } 

而不是锁定(这可能导致所有定时扫描等待并最终堆叠)。 您可以在一个线程中启动扫描/更新,然后只是检查线程是否仍然存在。

 Thread updateDBThread = new Thread(MyUpdateMethod); 

 private void timer_Elapsed(object sender, ElapsedEventArgs e) { if(!updateDBThread.IsAlive) updateDBThread.Start(); } 

您可以使用AutoResetEvent,如下所示:

 // Somewhere else in the code using System; using System.Threading; // In the class or whever appropriate static AutoResetEvent autoEvent = new AutoResetEvent(false); void MyWorkerThread() { while(1) { // Wait for work method to signal. if(autoEvent.WaitOne(30000, false)) { // Signalled time to quit return; } else { // grab a lock // do the work // Whatever... } } } 

一个稍微“更聪明”的解决方案如下伪代码:

 using System; using System.Diagnostics; using System.Threading; // In the class or whever appropriate static AutoResetEvent autoEvent = new AutoResetEvent(false); void MyWorkerThread() { Stopwatch stopWatch = new Stopwatch(); TimeSpan Second30 = new TimeSpan(0,0,30); TimeSpan SecondsZero = new TimeSpan(0); TimeSpan waitTime = Second30 - SecondsZero; TimeSpan interval; while(1) { // Wait for work method to signal. if(autoEvent.WaitOne(waitTime, false)) { // Signalled time to quit return; } else { stopWatch.Start(); // grab a lock // do the work // Whatever... stopwatch.stop(); interval = stopwatch.Elapsed; if (interval < Seconds30) { waitTime = Seconds30 - interval; } else { waitTime = SecondsZero; } } } } 

这两种方法都有一个优点,就是只需通过发出事件信号即可关闭线程。


编辑

我应该补充一点,这段代码假设您只运行其中一个MyWorkerThreads(),否则它们将同时运行。

当我想要单次执行时,我使用了互斥锁:

  private void OnMsgTimer(object sender, ElapsedEventArgs args) { // mutex creates a single instance in this application bool wasMutexCreatedNew = false; using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew)) { if (wasMutexCreatedNew) { try { // } finally { onlyOne.ReleaseMutex(); } } } } 

抱歉,我已经很晚了……您需要提供互斥锁名称作为GetMutexName()方法调用的一部分。