C# .NET 中最准确的计时器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9228313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Most accurate timer in .NET?
提问by maxp
Running the following (slightly pseudo)code produces the following results. Im shocked at how innacurate the timer is (gains ~14ms each Tick).
运行以下(稍微伪)代码会产生以下结果。我对计时器的不准确感到震惊(每个增益约 14 毫秒Tick)。
Is there anything more accurate out there?
那里有更准确的吗?
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
回答by Botz3000
Timer and DateTime do not have enough accuracy for your purpose. Try the Stopwatchinstead. Look at the following article for more details:
Timer 和 DateTime 没有足够的准确性来满足您的目的。试试秒表吧。有关更多详细信息,请查看以下文章:
http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx
http://blogs.msdn.com/b/ericlippert/archive/2010/04/08/precision-and-accuracy-of-datetime.aspx
回答by Dr. ABT
Its not the timer that is inaccurate, but DateTime.Now, which has an advertised tolerance of 16ms.
它不是不准确的计时器,而是DateTime.Now,它的广告容差为 16 毫秒。
Instead I would use the Environment.Ticksproperty to measure the CPU cycles during this test.
相反,我将使用Environment.Ticks属性来测量此测试期间的 CPU 周期。
Edit: Environment.Ticks is also based off the system timer and may have the same accuracy issues as DateTime.Now. I'd advise choosing the StopWatchinstead as many other answerers have mentioned.
编辑:Environment.Ticks 也基于系统计时器,可能与 DateTime.Now 具有相同的准确性问题。StopWatch正如许多其他回答者提到的那样,我建议选择。
回答by Shahar Gvirtz
Desktop operating system (such as windows) are notreal-time operating system. which means, you can't expect full accuracy and you can't force the scheduler to trigger your code in the exact millisecond you want. Specially in .NET application which is non-deterministic...for examply, any time the GC can start collecting, a JIT compilation might be a bit slower or a bit faster....
桌面操作系统(如 windows)不是实时操作系统。这意味着,您不能期望完全准确,也不能强制调度程序在您想要的精确毫秒内触发您的代码。特别是在非确定性的 .NET 应用程序中……例如,任何时候 GC 可以开始收集,JIT 编译可能会慢一点或快一点……
回答by haiyyu
I've made a class for that, and it seems to be working just fine. No inaccuracy whatsoever:
我为此做了一门课,它似乎工作得很好。没有任何不准确之处:
class AccurateTimer
{
public event EventHandler<EventArgs> 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<EventArgs> copy = Tick;
if (copy != null)
{
copy(this, e);
}
}
}
It might not be the best solution, though.
不过,这可能不是最好的解决方案。
回答by John
I also have witten a class which is accurate to 1ms. I took Hans Passant's code from forum
https://social.msdn.microsoft.com/Forums/en-US/6cd5d9e3-e01a-49c4-9976-6c6a2f16ad57/1-millisecond-timer
and wrapped it in a class for ease of use in your Form. You can easily set up multiple timers if you want. In the example code below I have used 2 timers. I have tested it and it works ok.
我还学习了一个精确到 1ms 的课程。我从论坛
https://social.msdn.microsoft.com/Forums/en-US/6cd5d9e3-e01a-49c4-9976-6c6a2f16ad57/1-millisecond-timer 中获取了 Hans Passant 的代码
并将其封装在一个类中以方便使用在您的表格中。如果需要,您可以轻松设置多个计时器。在下面的示例代码中,我使用了 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!
}
}
}
回答by Matt Thomas
I think the other answers are failing to address whythere's 14ms slew through each iteration of the OP's code; it's notbecause of an imprecise system clock (and DateTime.Nowis not inaccurate, unless you've turned off NTP services or have the wrong time zone set or something silly! It's only imprecise).
我认为其他答案未能解决为什么OP 代码的每次迭代都需要 14 毫秒的时间;这不是因为系统时钟不精确(并且DateTime.Now不是不准确,除非您关闭了 NTP 服务或设置了错误的时区或愚蠢的东西!只是不精确)。
Accurate timer
精准计时器
Even with an imprecise system clock (making use of DateTime.Now, or having a solar cell hooked up to an ADC to tell how high the sun is in the sky, or dividing the time between peak tides, or ...), code following this pattern will have an average of zero slew (it will be perfectly accurate with exactly one second between ticks on average):
即使使用不精确的系统时钟(利用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
}
(If you're copying-and-pasting this, watch out for cases where your tick code takes longer than intervalto execute. I'll leave it as an exercise for the reader to find the easy ways to make this skip as many beats as it takes for nextTickto land in the future)
(如果您正在复制和粘贴此内容,请注意您的滴答代码花费的时间比interval执行时间长的情况。我将其留作练习,让读者找到使此跳过尽可能多的节拍的简单方法需要nextTick在未来着陆)
Inaccurate timer
不准确的计时器
I'm guessing that Microsoft's implementation of System.Threading.Timer follows this kind of pattern instead. This pattern will always have slew even with a perfectly precise and perfectly accurate system timer (because it takes time to execute even just the add operation):
我猜微软对 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
}
So for folks who might be interested in rolling your own timer, don't follow this second pattern.
因此,对于可能对滚动自己的计时器感兴趣的人,请不要遵循第二种模式。
Precise time measurement
精确的时间测量
As other posters have said, the Stopwatchclass gives great precisionfor time measurement, but doesn't help at all with accuracyif the wrong pattern is followed. But, as @Shahar saidit's not like you're ever going to get a perfectly-precise timer to begin with, so you need to rethink things if perfect precisionis what you're after.
正如其他海报所说,该Stopwatch课程为时间测量提供了很高的精度,但如果遵循错误的模式,则对准确性没有任何帮助。但是,正如@Shahar 所说,您一开始就不会获得完美精确的计时器,因此,如果您追求完美的精确度,则需要重新考虑事情。
Disclaimers
免责声明
Note that Microsoft doesn't talk much about the internals of the System.Threading.Timerclass so I'm educatedly speculating about it, but if it quacks like a duck then it's probably a duck. Also, I realize this is several years old, but it's still a relevant (and I think unanswered) question.
请注意,Microsoft 并没有过多谈论System.Threading.Timer类的内部结构,因此我对它进行了有根据的推测,但如果它像鸭子一样嘎嘎叫,那么它可能就是鸭子。另外,我意识到这已经有好几年了,但它仍然是一个相关的(我认为没有答案的)问题。
Edit: Changed link to @Shahar's answer
编辑:更改了@Shahar 答案的链接
Edit: Microsoft has source code for a lot of stuff online, including System.Threading.Timer, for anyone who is interested in seeing how Microsoft implemented that slew-y timer
编辑:微软在网上有很多东西的源代码,包括System.Threading.Timer,对于任何有兴趣了解微软如何实现该回转定时器的人
回答by BobsMyUncle
Here is another approach. Accurate to within 5-20ms on my machine.
这是另一种方法。在我的机器上准确到 5-20 毫秒以内。
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;
}
}
回答by Mathijs Flietstra
This doesn't really make the timer more accurate (as in it doesn't make sure the time between callbacks is exactly 1 second), but if all you need is a timer which fires once every second and doesn't skip seconds because of the ~14msdrifting issue (as demonstrated in OP's sample output between the 17th and 19th second), you could simply change the timer to fire at the start of the upcoming second as soon as the callback fires (and obviously you could do the same for upcoming minute, upcoming hour and so on, if all you care about is making sure the interval doesn't drift):
这并没有真正使计时器更准确(因为它不能确保回调之间的时间正好是 1 秒),但是如果您只需要一个计时器,它每秒触发一次并且不会因为在~14ms漂移的问题(如在17和19秒之间OP的输出示例证明),你可以简单地在即将举行的第二次开始改变定时器火灾一旦回调火灾(显然你可以为即将到来分钟做同样的, 即将到来的小时等等,如果您只关心确保间隔不会漂移):
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
回答by Eike
For the record, this seems to be fixed nowadays.
根据记录,这似乎在今天已修复。
With OPs code, I get this in .NET Core 3.1:
使用 OPs 代码,我在 .NET Core 3.1 中得到了这个:
41.4263
42.4263
43.4291
44.4262
45.4261
46.4261
47.4261
48.4261
49.4260
50.4260
51.4260
52.4261

