如果前一个线程仍然忙,如何让Timer跳过勾选

我创建了一个Windows服务,它应该每隔60秒检查数据库中的某个表以获取新行。 对于添加的每个新行,我需要在服务器上进行一些繁重的处理,有时可能需要60秒以上。

我在我的服务中创建了一个Timer对象,每隔60秒就会调用一次,并调用所需的方法。
由于我不想在处理找到的新行时勾选此计时器,因此我将该方法包装在lock { }块中,因此其他线程无法访问此方法。

它看起来像这样:

 Timer serviceTimer = new Timer(); serviceTimer.Interval = 60; serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed); serviceTimer.Start(); void serviceTimer_Elapsed(object sender, ElapsedEventArgs e) { lock (this) { // do some heavy processing... } } 

现在,我想知道 –
如果我的计时器滴答,并在数据库上发现了很多新行,现在处理将花费超过60秒,下一个滴答将不会进行任何处理,直到前一个完成。 这就是我想要的效果。

但是现在,serviceTimer_Elapsed方法会在第一次处理完成后立即关闭,还是等待计时器再次打勾。

我想要发生的是 – 如果处理需要超过60秒,那么计时器会发现线程被锁定,再等待60秒再次检查,这样我就永远不会陷入线程队列的情况等待前一个完成。

我怎样才能完成这个结果?
这样做的最佳做法是什么?

谢谢!

您可以尝试在处理期间禁用计时器,例如

 // Just in case someone wants to inherit your class and lock it as well ... private static object _padlock = new object(); try { serviceTimer.Stop(); lock (_padlock) { // do some heavy processing... } } finally { serviceTimer.Start(); } 

编辑 :OP没有指定重入是仅由定时器引起还是服务是multithreading的。 假设后者,但如果前者然后锁定应该是不必要的,如果计时器停止(AutoReset或手动)

在这种情况下,您不需要锁。 在启动之前设置timer.AutoReset = false。 完成处理后,在处理程序中重新启动计时器。 这将确保计时器每个任务 60秒触发。

快速检查它是否正在运行。 如果它正在运行,它将跳过此事件并等待下一个事件触发。

 Timer serviceTimer = new Timer(); serviceTimer.Interval = 60; serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed); serviceTimer.Start(); bool isRunning = false; void serviceTimer_Elapsed(object sender, ElapsedEventArgs e) { lock (this) { if(isRunning) return; isRunning = true; } try { // do some heavy processing... } finally { isRunning = false; } } 

其他答案的类似变化,允许计时器保持滴答作响,只有在可以获得锁定时才能完成工作,而不是停止计时器。

把它放在经过的事件处理程序中:

 if (Monitor.TryEnter(locker) { try { // Do your work here. } finally { Monitor.Exit(locker); } } 

我建议您在处理时不要让计时器打勾。

将计时器AutoReset设置为false。 并在最后开始。 以下是您可能感兴趣的完整答案:需要从数据库中的作业队列执行作业的Windows服务; 通缉:示例代码

其他选项可能是使用BackGroundWorker类或TheadPool.QueueUserWorkItem。

后台工作人员可以轻松地为您提供当前正在进行的处理的选项检查,并一次处理1个项目。 ThreadPool将使您能够在每个tick(如有必要)继续将项目排队到后台线程。

根据您的描述,我假设您正在检查数据库中队列中的项目。 在这种情况下,我会使用ThreadPool将工作推送到后台,而不是减慢/停止检查机制。

对于服务,我建议你看一下使用ThreadPool方法 。 这样,您可以使用计时器每60秒检查一次新项目,然后将它们排队,然后让.Net计算出为每个项目分配的数量,并继续将项目推入队列。

例如:如果您只使用计时器并且有5个新行,则总共需要65秒的处理时间。 使用ThreadPool方法,这将在65秒内完成,有5个后台工作项。 使用Timer方法,这将需要4分钟以上(每行之间等待的时间),此外,这可能会导致正在排队的其他工作的后退日志。

以下是如何完成此操作的示例:

 Timer serviceTimer = new Timer(); void startTimer() { serviceTimer.Interval = 60; serviceTimer.Elapsed += new ElapsedEventHandler(serviceTimer_Elapsed); serviceTimer.AutoReset = false; serviceTimer.Start(); } void serviceTimer_Elapsed(object sender, ElapsedEventArgs e) { try { // Get your rows of queued work requests // Now Push Each Row to Background Thread Processing foreach (Row aRow in RowsOfRequests) { ThreadPool.QueueUserWorkItem( new WaitCallback(longWorkingCode), aRow); } } finally { // Wait Another 60 Seconds and check again serviceTimer.Stop(); } } void longWorkingCode(object workObject) { Row workRow = workObject as Row; if (workRow == null) return; // Do your Long work here on workRow } 

使用Reactive Extensions有一个很好的解决方法。 这是代码,你可以在这里阅读更全面的解释: http ://www.zerobugbuild.com/?p = 259

 public static IDisposable ScheduleRecurringAction( this IScheduler scheduler, TimeSpan interval, Action action) { return scheduler.Schedule( interval, scheduleNext => { action(); scheduleNext(interval); }); } 

你可以像这样使用它:

 TimeSpan interval = TimeSpan.FromSeconds(5); Action work = () => Console.WriteLine("Doing some work..."); var schedule = Scheduler.Default.ScheduleRecurringAction(interval, work); Console.WriteLine("Press return to stop."); Console.ReadLine(); schedule.Dispose(); 

另一个可能性是这样的:

 void serviceTimer_Elapsed(object sender, ElapsedEventArgs e) { if (System.Threading.Monitor.IsLocked(yourLockingObject)) return; else lock (yourLockingObject) // your logic ; }