C#午夜计时器过早调用

C#午夜计时器过早调用,c#,events,timer,C#,Events,Timer,我的系统需要每天午夜更新数据库。 该值是每日使用量计数器。 为了做到这一点,我对这个类进行了编码: internal class MidnightTimer { internal event EventHandler Elapsed = delegate { }; private Timer timer; internal MidnightTimer() { timer = new Timer(); timer.Elapsed +

我的系统需要每天午夜更新数据库。
该值是每日使用量计数器。
为了做到这一点,我对这个类进行了编码:

internal class MidnightTimer
{
    internal event EventHandler Elapsed = delegate { };
    private Timer timer;

    internal MidnightTimer()
    {
        timer = new Timer();
        timer.Elapsed += timer_Elapsed;
    }

    internal void Start()
    {
        TimeSpan timeSpanToMidnight = GetNextMidnight().Subtract(DateTime.Now);
        timer.Interval = timeSpanToMidnight.TotalMilliseconds;
        timer.Start();
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        Elapsed(this, EventArgs.Empty);
        timer.Stop();
        Start();
    }

    private static DateTime GetNextMidnight()
    {
        const string datePattern = "dd/MM/yyyy hh:mm:ss";
        const string dateFormat = "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}:{5:00}";
        DateTime nextMidnight;

        string dateString = string.Format(dateFormat, DateTime.Now.Day + 1, DateTime.Now.Month, DateTime.Now.Year, 0, 0, 0);
        bool valid = DateTime.TryParseExact(dateString, datePattern, CultureInfo.InvariantCulture, DateTimeStyles.None, out nextMidnight);

        if (!valid)
        {
            dateString = string.Format(dateFormat, 1, DateTime.Now.Month + 1, DateTime.Now.Year, 0, 0, 0);
            valid = DateTime.TryParseExact(dateString, datePattern, CultureInfo.InvariantCulture, DateTimeStyles.None, out nextMidnight);
        }

        if (!valid)
        {
            dateString = string.Format(dateFormat, 1, 1, DateTime.Now.Year + 1, 0, 0, 0);
            DateTime.TryParseExact(dateString, datePattern, CultureInfo.InvariantCulture, DateTimeStyles.None, out nextMidnight);
        }

        return nextMidnight;
    }
}
问题是它不准确。
有时会在午夜前几毫秒调用该事件,因为我在第一个事件中将其发送到DB后重置该值,所以它在第二个事件后重置DB中的值


非常感谢您的帮助。

您不能假设计时器事件以毫秒精度触发。精度取决于许多因素,包括系统在需要启动时的负载、进程优先级等。在这里,时钟偏移或任何时钟调整也会起作用,因为您正在计算一整天后将发生的事件


因为我假设,对于事件本身,它是否精确并不重要,我只是将上一个事件的时间保存在一个变量中,并将其用于计算下一个事件。如果您确实必须确保每晚只收到一个事件,那么您甚至必须在数据库中保存最后一个事件的时间

不能假定计时器事件以毫秒精度引发。精度取决于许多因素,包括系统在需要启动时的负载、进程优先级等。在这里,时钟偏移或任何时钟调整也会起作用,因为您正在计算一整天后将发生的事件


因为我假设,对于事件本身,它是否精确并不重要,我只是将上一个事件的时间保存在一个变量中,并将其用于计算下一个事件。如果您确实必须确保每晚只收到一个事件,那么您甚至必须在数据库中保存最后一个事件的时间

我建议您为错误设置一个余量,然后,当计时器过期时,如果您在午夜前的这段时间内,请重置计时器。您可以一直这样做,直到午夜后事件触发。

我建议您建立一个误差裕度,然后,当计时器过期时,如果您在午夜之前的这段时间内,请重置计时器。您可以一直这样做,直到午夜后引发事件。

以下是MidnightTimer类的新代码:

internal class MidnightTimer
{
    internal event EventHandler Elapsed = delegate { };
    private Timer timer;
    private DateTime previousRun;

    internal MidnightTimer()
    {
        SystemEvents.TimeChanged += SystemEvents_TimeChanged;

        timer = new Timer();
        timer.AutoReset = false;
        timer.Elapsed += timer_Elapsed;
    }

    internal void Start()
    {
        previousRun = DateTime.Today;

        TimeSpan timeSpanToMidnight = GetNextMidnight().Subtract(DateTime.Now);
        timer.Interval = timeSpanToMidnight.TotalMilliseconds;
        timer.Start();
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (previousRun != DateTime.Today)
            Elapsed(this, EventArgs.Empty);

        timer.Stop();
        Start();
    }

    private void SystemEvents_TimeChanged(object sender, EventArgs e)
    {
        timer.Stop();
        Start();
    }

    private static DateTime GetNextMidnight()
    {
        return DateTime.Today.AddDays(1);
    }
}
我通过@JonSkeet advice改进了
GetNextMidnight
函数。
我已将
AutoReset
属性设置为False。
我已经添加了计时器上一次运行日期与当前日期之间的比较检查。

我还注册了
SystemEvents.TimeChanged
事件,以便下次运行时重新计算计时器。

以下是MidnightTimer类的新代码:

internal class MidnightTimer
{
    internal event EventHandler Elapsed = delegate { };
    private Timer timer;
    private DateTime previousRun;

    internal MidnightTimer()
    {
        SystemEvents.TimeChanged += SystemEvents_TimeChanged;

        timer = new Timer();
        timer.AutoReset = false;
        timer.Elapsed += timer_Elapsed;
    }

    internal void Start()
    {
        previousRun = DateTime.Today;

        TimeSpan timeSpanToMidnight = GetNextMidnight().Subtract(DateTime.Now);
        timer.Interval = timeSpanToMidnight.TotalMilliseconds;
        timer.Start();
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if (previousRun != DateTime.Today)
            Elapsed(this, EventArgs.Empty);

        timer.Stop();
        Start();
    }

    private void SystemEvents_TimeChanged(object sender, EventArgs e)
    {
        timer.Stop();
        Start();
    }

    private static DateTime GetNextMidnight()
    {
        return DateTime.Today.AddDays(1);
    }
}
我通过@JonSkeet advice改进了
GetNextMidnight
函数。
我已将
AutoReset
属性设置为False。
我已经添加了计时器上一次运行日期与当前日期之间的比较检查。

我还注册了
SystemEvents.TimeChanged
事件,以便下次运行时重新计算计时器。

为什么要格式化字符串,然后对其进行解析,以获得下一个午夜?你希望它在这个月的最后一天做什么?我想您只需要
DateTime.Today+TimeSpan.FromDays(1)
。这可能对计时器的准确性没有帮助,但可以避免您复杂的代码。我尝试过您第二天的计算方法,但在一个月或/和闰年的最后一天出现了一些异常。在执行验证测试时,使用
DateTime.TryParseExact
函数,我已设法避免这些异常。不,如果使用
DateTime.Today+TimeSpan.FromDays(1)
,则不会有任何异常。你能得到什么例外?这确实是一种更好的方法。计时器的分辨率最多为16毫秒。关闭几毫秒是没有意义的。您永远无法改进这一点,因为您的代码没有运行在能够提供像这样的硬保证的实时操作系统上。当然,你也在测量程序的启动时间,这也是完全不可预测的。通过在午夜后5分钟运行代码,可以很容易地避免对代码运行的确切日期的依赖。为了避免在DB中获得两个输入,我目前正在00:00:05运行计时器。我正在寻找一个解决方案,如何在00:00:00时更新数据库。我本来想用它,但对于这么小的一个钉子来说,它似乎是一个更大的锤子。为什么你要格式化一个字符串,然后解析它,以便在下一个午夜得到它?你希望它在这个月的最后一天做什么?我想您只需要
DateTime.Today+TimeSpan.FromDays(1)
。这可能对计时器的准确性没有帮助,但可以避免您复杂的代码。我尝试过您第二天的计算方法,但在一个月或/和闰年的最后一天出现了一些异常。在执行验证测试时,使用
DateTime.TryParseExact
函数,我已设法避免这些异常。不,如果使用
DateTime.Today+TimeSpan.FromDays(1)
,则不会有任何异常。你能得到什么例外?这确实是一种更好的方法。计时器的分辨率最多为16毫秒。关闭几毫秒是没有意义的。您永远无法改进这一点,因为您的代码没有运行在能够提供像这样的硬保证的实时操作系统上。当然,你也在测量程序的启动时间,这也是完全不可预测的。通过在午夜后5分钟运行代码,可以很容易地避免对代码运行的确切日期的依赖。为了避免在DB中获得两个输入,我目前正在00:00:05运行计时器。我正在寻找一个解决方案,如何在00:00:00时更新数据库。我本来想用它,但对于这么小的钉子来说,它似乎是一把大得多的锤子。我怀疑