Timer once a minute on the minute

2019-07-14 06:27发布

I can't get the timer to fire once a minute on the minue, 1:00, 1:01, 1:02 etc. Instead, when the timer executes drifts by a couple of seconds each iteration

   internal void StartTimer()
    {
        DateTime nowEastern = CalendarEntity.Calendar.GetEasternTime();

        int secondsInterval = 5;

        double additionalSeconds = secondsInterval - nowEastern.TimeOfDay.TotalSeconds % secondsInterval;
        if (additionalSeconds == 0)
        {
            additionalSeconds = 1;
        }
        var nearestOnOneMinutes = new DateTime(
            nowEastern.Year,
            nowEastern.Month,
            nowEastern.Day,
            nowEastern.Hour,
            nowEastern.Minute,
            nowEastern.Second
        ).AddSeconds(additionalSeconds);

        TimeSpan timeToStart = nearestOnOneMinutes.Subtract(nowEastern);
        TimeSpan tolerance = TimeSpan.FromSeconds(1);
        if (timeToStart < tolerance)
        {
            timeToStart = TimeSpan.Zero;
        }

        timer_onem = new System.Threading.Timer(OnTimedEvent, null,
                                    (int)timeToStart.TotalMilliseconds, Timeout.Infinite);
    }

    private static void OnTimedEvent(object o)
    {
        var minute = DateTime.Now.Minute;
        var second = DateTime.Now.Second;
        if (minute != lastMinute && second % 60 < 2)
        {
            lastMinute = minute;
            CodeToExecute();
        }
    }

    static void CodeToExecute()
    {
        double tms = 60000;

        // code here
        int wait = 60 - System.DateTime.Now.Second;

        timer_onem.Change(Convert.ToInt64(tms) - wait, Timeout.Infinite);
    }

EDIT 1

I changed the interval so that it fires once a second and then check that the minute has changed. Still drifts

    timer_onem = new System.Threading.Timer(OnTimedEvent, null,
                                    (int)timeToStart.TotalMilliseconds, 1000);

    private static void OnTimedEvent(object o)
    {
        var minute = DateTime.Now.Minute;

        if (minute != lastMinute)
        {
            lastMinute = minute;
            CodeToExecute();
        }
    }

    private static void CodeToExecute()
    {
        if (bGenerate)
        {
            double tms = 1000;
            // code
            timer_onem.Change(Convert.ToInt64(tms), 1000);
        }
    }

1条回答
我欲成王,谁敢阻挡
2楼-- · 2019-07-14 07:06

A Timer is only guaranteed to be no faster than Interval.

So you need to call it, say every second and check for the full minute.

For even better precision you would have to check every 1/2 second or better.

It is a basic rule of information theory that says that to measure with a given resolution (1 second in your case) you need to sample with better than twice that resolution. Hence to measure 20kHz you need a smpling rate better than 2x20kHz, say 44.1kHz. (Recognize the numbers?)

If you don't want to call it so often for simply getting one precise point in time, you could write a little more involved code that on each Tick resets the Timer.Interval to a little under half of the remaining time until the next full minute until it is under say 500ms..

There are rather complex things going on in your code wrt to setting up the expected time, though; do make sure they are not the real problem. There should not be a growing drift from the timer's lack of precision, unless you 'collect' the errors..

查看更多
登录 后发表回答