【问题标题】:For timer precision - should I use period or set new timer?对于计时器精度 - 我应该使用周期还是设置新计时器?
【发布时间】:2016-05-03 10:45:14
【问题描述】:

我需要每分钟准确地做一些事情。

目前,我正在使用:

int delayMS = (60 - DateTime.Now.Second) * 1000;
int periodMS = 60000;

BarTimer = new System.Threading.Timer(new TimerCallback(BarEndProcess), null, delayMS, periodMS);

这是否可能比每分钟重新启动计时器更精确,例如:

int delayMS = (60 - DateTime.Now.Second) * 1000;

BarTimer = new System.Threading.Timer(new TimerCallback(BarEndProcess), null, delayMS, 0);

我希望 CallBack 尽可能靠近分钟边界触发。

【问题讨论】:

  • 我怀疑它会有很大的不同。 “精确”有多精确?定时器的分辨率可能小于 50ms,但通常在 15ms 左右。
  • 我需要每分钟精确地做一些事情 我没有实时操作系统
  • @Groo 我不明白你的评论
  • 定时器在windows下的分辨率很差,如@MatthewWatson所说,15到50毫秒,如果你需要非常精确的执行,那么你可以使用定时器+秒表的组合,启动定时器58秒并同时启动秒表,然后当您的计时器触发时,您可以检查实际经过的量,并执行 sleep(0) 直到秒表达到准确的 60 秒。
  • @ManInMoon 实时操作系统可以保证在给定时间精确执行某些操作。 Windows 不是实时操作系统。也就是说,在Windows下总会存在一定程度的不确定性。

标签: c# timing


【解决方案1】:

您似乎需要在每次触发时重新同步您的计时器,以防止经过的时间间隔漂移。

这是我编写的一个测试程序,用于查看计时器回调是否会漂移。此示例尝试每 10 秒获取一次回调:

using System;
using System.Diagnostics;
using System.Threading;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var timer = new Timer(test, null, 10000, 10000);
            Console.ReadLine();
            GC.KeepAlive(timer);
        }

        static void test(object obj)
        {
            if (stopwatch == null)
                stopwatch = Stopwatch.StartNew();

            Console.WriteLine(stopwatch.ElapsedMilliseconds);
        }

        static Stopwatch stopwatch;
    }
}

由此产生的结果(超过午餐时间!)是:

0
9995
19996
29997
39997
49998
59998
69999
80000
90000
100001
110002
120002
130003
140003
150004
160004
170005
180006
190007
200008
210009
220009
230010
240011
250012
260013
270014
280014
...
2800158
2810158
2820159
2830160
2840160
2850161
2860161
2870162
2880163
2890163
2900164
2910165
2920166
2930166
2940167
2950168

如您所见,回调发生的时间逐渐偏离每 10 秒一次。

您可以通过测量绝对经过时间并调整下一个报价的回调时间来解决此问题。 Stopwatch 应该适合测量经过的时间,但您也可以使用 DateTime.Now 来做到这一点。

这是一个使用Stopwatch 重新校准计时器的示例程序。此示例尝试每 10 秒获取一次回调。

using System;
using System.Diagnostics;
using System.Threading;

namespace Demo
{
    static class Program
    {
        const int PERIOD_MILLISECONDS = 10000;

        static void Main()
        {
            timer = new Timer(test, null, PERIOD_MILLISECONDS, PERIOD_MILLISECONDS);
            Console.ReadLine();
            GC.KeepAlive(timer);
        }

        static void test(object dummy)
        {
            if (stopwatch == null)
                stopwatch = Stopwatch.StartNew();

            Console.WriteLine(stopwatch.ElapsedMilliseconds);

            long newPeriod = PERIOD_MILLISECONDS - stopwatch.ElapsedMilliseconds + expectedElapsedMillseconds;
            timer.Change(newPeriod, PERIOD_MILLISECONDS);

            expectedElapsedMillseconds += PERIOD_MILLISECONDS;
        }

        static Stopwatch stopwatch;
        static long      expectedElapsedMillseconds;
        static Timer     timer;
    }
}

这个输出看起来像这样:

0
10010
20014
30011
40013
50009
60016
70001
80000
90001
100000
110000
120000
130001
140001
150001
160000
170001
180001
190001
200001
210001
220001
230001
240001
250001
260001
270001
280000
290000
300001
310000
320000
330001
340001
350001
360001

一开始有轻微的晃动,但注意回调时间总是在应该发生的 16 毫秒内,并且没有漂移。

【讨论】:

  • 感谢您的调查。奇怪的那一秒应该更精确,因为@Groo 认为计时器在内部以相同的方式重置回调。
  • @ManInMoon 不,Groo 是在谈论创建新计时器和重用现有计时器之间的区别(即没有区别)。关于漂移他说:From what I see in the source System.Threading.Timer doesn't do anything to minimize drift.
猜你喜欢
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 2018-10-17
相关资源
最近更新 更多