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

C# System.Threading.Timer调用每天漂移几秒钟,c#,time,windows-services,timer,C#,Time,Windows Services,Timer,我有一个一直在运行的服务,它有一个计时器,每天凌晨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); } _dailyNodalRunT

我有一个一直在运行的服务,它有一个计时器,每天凌晨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
正如你所看到的,它每天漂移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
所以我想最好的方法就是重新安排滴答处理程序中的计时器?以下内容在~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是一个简单的, 轻量级计时器。。。对于基于服务器的计时器功能,您可以考虑使用,这会引发事件并具有附加特性。


你也可以将它移动到

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


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

不要让计时器不准确累积。使用RTC计算超时时间前剩余的毫秒数。睡眠/将间隔时间设置为这次的一半。当计时器触发/休眠返回时,再次使用RTC重新计算剩余间隔,并再次将间隔/休眠设置为半衰期。重复此循环,直到剩余间隔小于50毫秒。然后CPU在RTC上循环,直到超过所需时间。取消该活动

Rgds,
Martin

在.NET框架中没有一个计时器是准确的。有太多的变数在起作用。如果你想要一个更精确的计时器,那么看看。我从未在更长的时间内使用过它们,但我怀疑它们仍然比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
只是添加了一些方便的功能,如自动重置和编组
已运行的执行
ISynchronizeInvoke
托管线程(通常是UI线程)。

函数需要2秒吗?计时器刚刚启动事件,它是否真的在重新安排事件之前等待工作线程完成?我不确定该功能需要多长时间,可能需要2秒钟左右。使用windows计划的任务不是更好吗。
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