.NET中最准确的计时器?

运行以下(略微伪)代码会产生以下结果。 我对计时器的真空程度感到震惊(每个Tick增加~14ms)。

那里有更准确的东西吗?

 void Main() { var timer = new System.Threading.Timer(TimerCallback, null, 0, 1000); } void TimerCallback(object state) { Debug.WriteLine(DateTime.Now.ToString("ss.ffff")); } Sample Output: ... 11.9109 12.9190 13.9331 14.9491 15.9632 16.9752 17.9893 19.0043 20.0164 21.0305 22.0445 23.0586 24.0726 25.0867 26.1008 27.1148 28.1289 29.1429 30.1570 31.1710 32.1851 

要准确测量时间,您需要使用秒表类MSDN

我也有一个精确到1ms的课程。 我从论坛上拿了Hans Passant的代码
https://social.msdn.microsoft.com/Forums/en-US/6cd5d9e3-e01a-49c4-9976-6c6a2f16ad57/1-millisecond-timer
并将其包装在一个类中,以便在您的表单中使用。 如果需要,您可以轻松设置多个计时器。 在下面的示例代码中,我使用了2个计时器。 我测试了它,它工作正常。

 // AccurateTimer.cs using System; using System.Windows.Forms; using System.Runtime.InteropServices; namespace YourProjectsNamespace { class AccurateTimer { private delegate void TimerEventDel(int id, int msg, IntPtr user, int dw1, int dw2); private const int TIME_PERIODIC = 1; private const int EVENT_TYPE = TIME_PERIODIC;// + 0x100; // TIME_KILL_SYNCHRONOUS causes a hang ?! [DllImport("winmm.dll")] private static extern int timeBeginPeriod(int msec); [DllImport("winmm.dll")] private static extern int timeEndPeriod(int msec); [DllImport("winmm.dll")] private static extern int timeSetEvent(int delay, int resolution, TimerEventDel handler, IntPtr user, int eventType); [DllImport("winmm.dll")] private static extern int timeKillEvent(int id); Action mAction; Form mForm; private int mTimerId; private TimerEventDel mHandler; // NOTE: declare at class scope so garbage collector doesn't release it!!! public AccurateTimer(Form form,Action action,int delay) { mAction = action; mForm = form; timeBeginPeriod(1); mHandler = new TimerEventDel(TimerCallback); mTimerId = timeSetEvent(delay, 0, mHandler, IntPtr.Zero, EVENT_TYPE); } public void Stop() { int err = timeKillEvent(mTimerId); timeEndPeriod(1); System.Threading.Thread.Sleep(100);// Ensure callbacks are drained } private void TimerCallback(int id, int msg, IntPtr user, int dw1, int dw2) { if (mTimerId != 0) mForm.BeginInvoke(mAction); } } } // FormMain.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace YourProjectsNamespace { public partial class FormMain : Form { AccurateTimer mTimer1,mTimer2; public FormMain() { InitializeComponent(); } private void FormMain_Load(object sender, EventArgs e) { int delay = 10; // In milliseconds. 10 = 1/100th second. mTimer1 = new AccurateTimer(this, new Action(TimerTick1),delay); delay = 100; // 100 = 1/10th second. mTimer2 = new AccurateTimer(this, new Action(TimerTick2), delay); } private void FormMain_FormClosing(object sender, FormClosingEventArgs e) { mTimer1.Stop(); mTimer2.Stop(); } private void TimerTick1() { // Put your first timer code here! } private void TimerTick2() { // Put your second timer code here! } } } 

我认为其他答案未能解决为什么在OP的代码的每次迭代中都有14ms的转换; 这不是因为系统时钟不精确(并且DateTime.Now不准确,除非您关闭了NTP服务或设置了错误的时区或者傻事!这只是不精确的 )。

准确的计时器

即使有一个不精确的系统时钟(利用DateTime.Now ,或者将太阳能电池连接到ADC来判断太阳在天空中的高度,或者在峰值潮汐之间划分时间,或者……),遵循这种模式将具有平均零转换(它将是完全准确的,平均每个刻度之间只有一秒):

 var interval = new TimeSpan(0, 0, 1); var nextTick = DateTime.Now + interval; while (true) { while ( DateTime.Now < nextTick ) { Thread.Sleep( nextTick - DateTime.Now ); } nextTick += interval; // Notice we're adding onto when the last tick was supposed to be, not when it is now // Insert tick() code here } 

(如果你正在复制并粘贴它,请注意你的刻度代码需要比执​​行interval更长的情况。我会留下它作为练习让读者找到简单的方法来使这个跳过多次节拍因为nextTick将来降落需要)

不准确的计时器

我猜测Microsoft的System.Threading.Timer实现遵循这种模式。 即使只使用完美精确且完全精确的系统计时器,这种模式也总能摆动(因为即使只是添加操作也需要时间):

 var interval = new TimeSpan(0, 0, 1); var nextTick = DateTime.Now + interval; while (true) { while ( DateTime.Now < nextTick ) { Thread.Sleep( nextTick - DateTime.Now ); } nextTick = DateTime.Now + interval; // Notice we're adding onto .Now instead of when the last tick was supposed to be. This is where slew comes from // Insert tick() code here } 

因此对于那些可能有兴趣推出自己的计时器的人来说,不要遵循这第二种模式。

精确的时间测量

正如其他海报所说, Stopwatch类为时间测量提供了极高的精确度 ,但如果遵循错误的模式,则完全没有准确性 。 但是, 正如@Shahar说的那样,你不会想要一个完美精确的计时器开始,所以你需要重新思考,如果完美的精确度是你所追求的。

免责声明

请注意,微软并没有多少谈论System.Threading.Timer类的内部,所以我在教育上推测它,但如果它像鸭子一样嘎嘎叫,那么它可能是一个鸭子。 此外,我意识到这已经有好几年了,但它仍然是一个相关的(我认为没有答案)问题。

编辑:更改了@ Shahar答案的链接

编辑:微软有很多在线内容的源代码,包括System.Threading.Timer ,适用于有兴趣了解微软如何实现该计时器的人

计时器和日期时间没有足够的准确性用于您的目的。 请尝试使用秒表 。 请查看以下文章以获取更多详细信息:

http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx

桌面操作系统(如windows) 不是实时操作系统。 这意味着,您不能期望完全准确,并且您无法强制调度程序以您想要的精确毫秒触发代码。 特别是在.NET应用程序中,这是非确定性的……例如,只要GC可以开始收集,JIT编译可能会慢一点或者更快一点……

它不是不准确的计时器,而是DateTime.Now ,其广告容差为16ms。

相反,我会使用Environment.Ticks属性来测量此测试期间的CPU周期。

编辑 :Environment.Ticks也基于系统计时器,可能与DateTime.Now具有相同的准确性问题。 我建议选择StopWatch就像许多其他回答者提到的那样。

这是另一种方法。 在我的机器上精确到5-20ms。

 public class Run { public Timer timer; public Run() { var nextSecond = MilliUntilNextSecond(); var timerTracker = new TimerTracker() { StartDate = DateTime.Now.AddMilliseconds(nextSecond), Interval = 1000, Number = 0 }; timer = new Timer(TimerCallback, timerTracker, nextSecond, -1); } public class TimerTracker { public DateTime StartDate; public int Interval; public int Number; } void TimerCallback(object state) { var timeTracker = (TimerTracker)state; timeTracker.Number += 1; var targetDate = timeTracker.StartDate.AddMilliseconds(timeTracker.Number * timeTracker.Interval); var milliDouble = Math.Max((targetDate - DateTime.Now).TotalMilliseconds, 0); var milliInt = Convert.ToInt32(milliDouble); timer.Change(milliInt, -1); Console.WriteLine(DateTime.Now.ToString("ss.fff")); } public static int MilliUntilNextSecond() { var time = DateTime.Now.TimeOfDay; var shortTime = new TimeSpan(0, time.Hours, time.Minutes, time.Seconds, 0); var oneSec = new TimeSpan(0, 0, 1); var milliDouble = (shortTime.Add(oneSec) - time).TotalMilliseconds; var milliInt = Convert.ToInt32(milliDouble); return milliInt; } } 

这并没有真正使定时器更准确(因为它不能确保回调之间的时间恰好是1秒 ),但是如果您只需要一个定时器,它每秒触发一次并且不会跳过秒,因为~14ms漂移问题(如OP在第17秒和第19秒之间的样本输出中所示),你可以简单地将定时器更改为在回调激发时即将到来的第二个开始时触发(显然你可以做同样的事情)即将到来的分钟,即将到来的小时等,如果您关心的是确保间隔不漂移):

 using System.Threading; static Timer timer; void Main() { // 1000 - DateTime.UtcNow.Millisecond = number of milliseconds until the next second timer = new Timer(TimerCallback, null, 1000 - DateTime.UtcNow.Millisecond, 0); } void TimerCallback(object state) { // Important to do this before you do anything else in the callback timer.Change(1000 - DateTime.UtcNow.Millisecond, 0); Debug.WriteLine(DateTime.UtcNow.ToString("ss.ffff")); } Sample Output: ... 25.0135 26.0111 27.0134 28.0131 29.0117 30.0135 31.0127 32.0104 33.0158 34.0113 35.0129 36.0117 37.0127 38.0101 39.0125 40.0108 41.0156 42.0110 43.0141 44.0100 45.0149 46.0110 47.0127 48.0109 49.0156 50.0096 51.0166 52.0009 53.0111 54.0126 55.0116 56.0128 57.0110 58.0129 59.0120 00.0106 01.0149 02.0107 03.0136 

我为此做了一堂课,似乎工作得很好。 没有任何不准确之处:

 class AccurateTimer { public event EventHandler Tick; public bool Running { get; private set; } public int Interval { get; private set; } public AccurateTimer(int interval_ = 1000) { Running = false; Interval = interval_; } public void Start() { Running = true; Thread thread = new Thread(Run); thread.Start(); } public void Stop() { Running = false; } private void Run() { DateTime nextTick = DateTime.Now.AddMilliseconds(Interval); while (Running) { if (DateTime.Now > nextTick) { nextTick = nextTick.AddMilliseconds(Interval); OnTick(EventArgs.Empty); } } } protected void OnTick(EventArgs e) { EventHandler copy = Tick; if (copy != null) { copy(this, e); } } } 

不过,它可能不是最好的解决方案。