2011-06-06 95 views
4

我有一个始终运行的服务,它有一个定时器,每天凌晨2点执行一个特定的操作。System.Threading.Timer调用每天漂移几秒钟

TimeSpan runTime = new TimeSpan(2, 0, 0); // 2 AM 
TimeSpan timeToFirstRun = runTime - DateTime.Now.TimeOfDay; 

if (timeToFirstRun.TotalHours < 0) 
{ 
    timeToFirstRun += TimeSpan.FromDays(1.0); 
} 

_dailyNodalRunTimer = new Timer(
    RunNodalDailyBatch, 
    null, 
    timeToFirstRun, 
    TimeSpan.FromDays(1.0)); //repeat event daily 

,首先启动该服务时,初始化代码被调用一次,在过去的几天里,当计时器已经解雇我已经登录:

2011-05-21 02:00:01.580 
2011-05-22 02:00:03.840 
... 
2011-05-31 02:00:25.227 
2011-06-01 02:00:27.423 
2011-06-02 02:00:29.847 

正如你可以减少2秒每一个看到它的漂流一天,从应该开火的时间越来越远(凌晨2点)。

我使用它是错的还是这个计时器没有被设计为准确?我可以每天重新创建计时器,或者在一小段时间内重新启动计时器,然后重复检查是否要执行该操作,但这看起来有点不方便。

编辑

我尝试使用System.Timers.Timer,它似乎有同样的问题。在正在重置的间隔是因为你不能在System.Timers.Timer第一前打勾安排的初始时间就像你可以在System.Threading.Timer

int secondsInterval = 5; 

double secondsUntilRunFirstRun = secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval); 
var timer = new System.Timers.Timer(secondsUntilRunFirstRun * 1000.0); 
timer.AutoReset = true; 
timer.Elapsed += (sender, e) => 
    { 
     Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff")); 

     if (timer.Interval != (secondsInterval * 1000.0)) 
      timer.Interval = secondsInterval * 1000.0; 
    }; 
timer.Start(); 

产生以下时间,你可以看到他们是如何漂流稍微:

06:47:40.020 
06:47:45.035 
06:47:50.051 
... 
06:49:40.215 
06:49:45.223 
06:49:50.232 

所以我想最好的方法是真的只是重新安排滴答处理程序中的计时器?下面以一定间隔产生蜱内〜15毫秒

double secondsUntilRunFirstRun = secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval); 

var timer = new System.Timers.Timer(secondsUntilRunFirstRun * 1000.0); 
timer.AutoReset = false; 
timer.Elapsed += (sender, e) => 
{ 
    Console.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff")); 

    timer.Interval = (secondsInterval - (DateTime.Now.TimeOfDay.TotalSeconds % secondsInterval)) * 1000.0; 
}; 
timer.Start(); 


06:51:45.009 
06:51:50.001 
... 
06:52:50.011 
06:52:55.013 
06:53:00.001 
+2

功能需要2秒吗? – rerun 2011-06-06 23:24:27

+0

计时器刚刚启动事件,是否真的要等待工作线程在重新计划事件之前完成?我不知道该功能需要多久才能完全是,它可以在2秒左右 – BrandonAGr 2011-06-07 00:09:06

+1

那岂不是更好地使用计划任务窗口。 – rerun 2011-06-07 00:21:45

回答

3

在.NET Framework中的计时器都不会是准确的。游戏中有太多变数。如果你想要更精确的计时器,那么看看multimedia timers。我从来没有用过它们,但是我怀疑它们比BCL定时器还要精确得多。

但是,我看不出有什么理由阻止你使用System.Threading.Timer类。而不是指定TimeSpan.FromDays(1)使用Timeout.Infinite来防止定期信号。然后您必须重新启动定时器,但您可以指定参数为dueTime的参数为23:59:58或1.00:00:05,具体取决于计算下一个到期时间为2:00a的信号。

顺便说一句,在System.Timers.Timer会做不超过System.Threading.Timer更好。原因是因为前者实际上是在幕后使用后者。 System.Timers.Timer只是增加了一些方便的功能,如自动复位和编组的Elapsed执行到一个ISynchronizeInvoke托管线程(通常是UI线程)。

1

msdn

System.Threading.Timer是一个简单的,轻量级的 定时器......对于基于服务器的定时器功能,你可以考虑使用System.Timers.Timer ,这引发了事件并具有附加功能。

你也可以把它移到Windows Task Scheduler

3

我想你已经意识到了这一点,但如果你想要的东西火在一天的某个时间(2AM),你会用专用的更好睡觉的线程,定期醒来,看看是否该运行了。大约100毫秒的睡眠将是合适的,并且几乎不会烧毁CPU。

另一种方法是,在完成日常工作后,根据明天2AM计算何时到下一次着火 - DateTime.Current等。这可能仍然不如您想要的那么准确(我不是当然),但至少这个漂移不会越来越糟,越来越糟。

4

不要让计时器的不准确性积累。使用RTC计算在超时时间之前保留多少毫秒。睡眠/ setInterval时间减半。当定时器触发/睡眠返回时,再次使用RTC重新计算剩余时间间隔,并将间隔/睡眠时间再次设为半衰期。重复此循环,直到剩余时间间隔小于50毫秒。然后CPU在RTC上循环,直到超过所需的时间。启动事件。

RGDS, 马丁