【问题标题】:System.Threading.Timer call drifts a couple seconds every daySystem.Threading.Timer 调用每天漂移几秒钟
【发布时间】:2011-09-09 16:32:00
【问题描述】:

我有一个始终在运行的服务,它有一个计时器,可以在每天凌晨 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

该初始化代码在服务首次启动时调用一次,过去几天我在 Timer 触发时记录了:

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 点)。

是我用错了还是这个定时器设计不准确?我可以每天重新创建计时器,或者让它以一些小的间隔触发并反复检查我是否要执行该操作,但这似乎有点 hacky。

编辑

我尝试使用 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

所以我想最好的方法真的是在滴答处理程序中重新安排计时器?以下在约 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 秒左右
  • 使用windows计划任务不是更好吗。

标签: c# time windows-services timer


【解决方案1】:

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

Rgds, 马丁

【讨论】:

    【解决方案2】:

    .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 线程)上。

    【讨论】:

      【解决方案3】:

      我想你已经意识到了这一点,但是如果你想在一天中的某个时间(凌晨 2 点)触发某些东西,你最好使用一个专门的线程来休眠,定期唤醒并查看是否是时间运行呢。大约 100 毫秒的睡眠是合适的,并且几乎不会消耗 CPU。

      另一种方法是,在您完成日常工作后,根据明天凌晨 2 点计算下一次点火的时间 - DateTime.Current 等。这可能仍然不如您想要的准确(我不确定) 但至少漂移不会越来越差。

      【讨论】:

        【解决方案4】:

        如果您需要准确的计时,则需要System.Timers.Timer 类。

        另见这个问题:.NET, event every minute (on the minute). Is a timer the best option?

        【讨论】:

          【解决方案5】:

          来自msdn

          System.Threading.Timer 很简单, 轻量级计时器...对于基于服务器的计时器功能,您可以考虑使用System.Timers.Timer,它会引发事件并具有附加功能。

          您也可以将其移至Windows Task Scheduler

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-18
            • 1970-01-01
            • 2017-06-08
            • 2012-07-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多