Running the following (slightly pseudo)code produces the following results. Im shocked at how innacurate the timer is (gains ~14ms each 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
Atomic clocks are so accurate that they will lose one second approximately every 100 million years; for reference, the average quartz clock will lose one second every couple of years. On the other hand, Ye's optical lattice clock will lose one second every 15 billion years, making it the world's most accurate clock.
I think the other answers are failing to address why there's 14ms slew through each iteration of the OP's code; it's not because of an imprecise system clock (and DateTime.Now
is not inaccurate, unless you've turned off NTP services or have the wrong time zone set or something silly! It's only imprecise).
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):
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 interval
to 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 nextTick
to land in the future)
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):
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.
As other posters have said, the Stopwatch
class gives great precision for time measurement, but doesn't help at all with accuracy if the wrong pattern is followed. But, as @Shahar said it's not like you're ever going to get a perfectly-precise timer to begin with, so you need to rethink things if perfect precision is what you're after.
Note that Microsoft doesn't talk much about the internals of the System.Threading.Timer class 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.
Edit: Changed link to @Shahar's answer
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
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.
// 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! } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With