System.Threading.Timer调用每天漂移几秒钟
本文关键字:几秒 漂移 Threading Timer 调用 每天 System | 更新日期: 2023-09-27 17:58:36
我有一个服务一直在运行,它有一个计时器,每天凌晨2点执行一个特定的操作。
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.Threading.Timer 中那样,在System.Timers.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
所以我想最好的方法是重新安排tick处理程序中的计时器?以下在约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
不要让计时器的不准确性累积。使用RTC计算在超时时间之前还有多少毫秒。睡眠/setInterval设置为此时间的一半。当计时器触发/睡眠返回时,再次使用RTC重新计算剩余间隔,并将间隔/睡眠再次设置为半衰期。重复此循环,直到剩余时间间隔小于50ms。然后CPU在RTC上循环,直到超过所需时间。启动活动。
Rgds,Martin
.NET Framework中的计时器都不准确。有太多的变数在起作用。如果你想要一个更准确的定时器,那么看看多媒体定时器。我从未在更长的时间内使用过它们,但我怀疑它们仍然比BCL计时器准确得多。
但是,我看不出有什么理由会禁止您使用System.Threading.Timer
类。使用Timeout.Infinite
来防止周期性信令,而不是指定TimeSpan.FromDays(1)
。然后,您必须重新启动计时器,但您可以为dueTime
参数指定23:59:58或1.00:00:05,具体取决于您计算的下一个到期时间为2:00a。
顺便说一下,System.Timers.Timer
不会比System.Threading.Timer
做得更好。原因是前者实际上在幕后使用了后者。System.Timers.Timer
只是添加了一些方便的功能,如自动重置和将Elapsed
的执行封送到ISynchronizeInvoke
托管的线程(通常是UI线程)上。
我想你已经意识到了这一点,但如果你想在一天中的某个时间(凌晨2点)启动一些东西,最好有一个专门的线程,它可以睡眠、定期唤醒并查看是否到了运行的时候。大约100毫秒的睡眠是合适的,并且几乎不会消耗CPU。
另一种方法是,在你完成日常工作后,你根据明天凌晨2点计算下一次火灾的时间——DateTime.Current等。这可能仍然没有你想要的那么准确(我不确定),但至少漂移不会越来越糟。
如果需要精确的计时,则需要System.Timers.Timer类。
另请参阅此问题:.NET,每分钟事件(按分钟)。定时器是最好的选择吗?
来自msdn:
System.Threading.Timer是一个简单的,轻量级计时器。。。对于基于服务器的计时器功能,可以考虑使用System.Timers.timer,它会引发事件并具有其他功能。
您也可以将其移动到Windows任务调度程序