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

System.Threading.Timer调用每天漂移几秒钟

不要让计时器的不准确性累积。使用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任务调度程序