C#午夜计时器过早地被调用

时间:2014-01-12 07:56:25

标签: c# events timer

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

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中的值。

感谢任何帮助。

3 个答案:

答案 0 :(得分:1)

您不能假设以毫秒精度引发的计时器事件。精度取决于很多因素,包括系统在需要触发时的负载,过程优先级等。此时,时钟偏差或任何时钟调整都会发挥作用,因为您正在计算事件这将在一整天后发生。

由于我假设对于事件本身来说,它并不重要,因为它不精确,我只是将最后一个事件的时间保存在一个变量中并用它来计算下一个事件。如果你真的必须确保每晚只有一个事件,你甚至必须将最后一个事件的时间保存在数据库中。

答案 1 :(得分:0)

我建议你建立一个误差范围,然后,当你的计时器过去时,如果你在午夜之前的那段时间内,重置计时器。你可以继续这样做,直到午夜之后提出这个事件。

答案 2 :(得分:0)

以下是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建议改进了GetNextMidnight功能 我将AutoReset属性设置为False 我在计时器的上一个运行日期和当前日期之间添加了一个比较检查 我还注册了SystemEvents.TimeChanged事件,以便重新计算下次运行的计时器。