Reputation: 25141
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
Upvotes: 51
Views: 64135
Reputation: 8817
Some years later but here is what I came up with. It alines itself and is usually accurate to under 1ms. In a nutshell, it starts with a low CPU intensive Task.Delay and moves up to a spinwait. it is usually accurate to about 50µs (0.05 ms).
static void Main()
{
PrecisionRepeatActionOnIntervalAsync(SayHello(), TimeSpan.FromMilliseconds(1000)).Wait();
}
// Some Function
public static Action SayHello() => () => Console.WriteLine(DateTime.UtcNow.ToString("ss.ffff"));
public static async Task PrecisionRepeatActionOnIntervalAsync(Action action, TimeSpan interval, CancellationToken? ct = null)
{
long stage1Delay = 20 ;
long stage2Delay = 5 * TimeSpan.TicksPerMillisecond;
bool USE_SLEEP0 = false;
DateTime target = DateTime.UtcNow + new TimeSpan(0, 0, 0, 0, (int)stage1Delay + 2);
bool warmup = true;
while (true)
{
// Getting closer to 'target' - Lets do the less precise but least cpu intesive wait
var timeLeft = target - DateTime.UtcNow;
if (timeLeft.TotalMilliseconds >= stage1Delay)
{
try
{
await Task.Delay((int)(timeLeft.TotalMilliseconds - stage1Delay), ct ?? CancellationToken.None);
}
catch (TaskCanceledException) when (ct != null)
{
return;
}
}
// Getting closer to 'target' - Lets do the semi-precise but mild cpu intesive wait - Task.Yield()
while (DateTime.UtcNow < target - new TimeSpan(stage2Delay))
{
await Task.Yield();
}
// Getting closer to 'target' - Lets do the semi-precise but mild cpu intensive wait - Thread.Sleep(0)
// Note: Thread.Sleep(0) is removed below because it is sometimes looked down on and also said not good to mix 'Thread.Sleep(0)' with Tasks.
// However, Thread.Sleep(0) does have a quicker and more reliable turn around time then Task.Yield() so to
// make up for this a longer (and more expensive) Thread.SpinWait(1) would be needed.
if (USE_SLEEP0)
{
while (DateTime.UtcNow < target - new TimeSpan(stage2Delay / 8))
{
Thread.Sleep(0);
}
}
// Extreamlly close to 'target' - Lets do the most precise but very cpu/battery intesive
while (DateTime.UtcNow < target)
{
Thread.SpinWait(64);
}
if (!warmup)
{
await Task.Run(action); // or your code here
target += interval;
}
else
{
long start1 = DateTime.UtcNow.Ticks + ((long)interval.TotalMilliseconds * TimeSpan.TicksPerMillisecond);
long alignVal = start1 - (start1 % ((long)interval.TotalMilliseconds * TimeSpan.TicksPerMillisecond));
target = new DateTime(alignVal);
warmup = false;
}
}
}
Sample output:
07.0000
08.0000
09.0000
10.0001
11.0000
12.0001
13.0000
14.0000
15.0000
16.0000
17.0000
18.0000
19.0001
20.0000
21.0000
22.0000
23.0000
24.0000
25.0000
26.0000
27.0000
28.0000
29.0000
30.0000
31.0000
32.0138 <---not that common but can happen
33.0000
34.0000
35.0001
36.0000
37.0000
38.0000
39.0000
40.0000
41.0000
Upvotes: 18
Reputation: 39600
Timer and DateTime do not have enough accuracy for your purpose. Try the Stopwatch instead. Look at the following article for more details:
https://learn.microsoft.com/en-us/archive/blogs/ericlippert/precision-and-accuracy-of-datetime
Upvotes: 4
Reputation: 5744
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.
Upvotes: 24
Reputation: 1311
For the record, this seems to be fixed nowadays.
With OPs code, I get this in .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
Upvotes: 6
Reputation: 12974
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 ~14ms
drifting 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):
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
Upvotes: 1
Reputation: 1
Here is another approach. Accurate to within 5-20ms on my machine.
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;
}
}
Upvotes: 0
Reputation: 311
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!
}
}
}
Upvotes: 31
Reputation: 2242
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.
Upvotes: -4
Reputation: 21521
Its not the timer that is inaccurate, but DateTime.Now, which has an advertised tolerance of 16ms.
Instead I would use the Environment.Ticks property to measure the CPU cycles during this test.
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 StopWatch
instead as many other answerers have mentioned.
Upvotes: 3
Reputation: 2438
Desktop operating system (such as windows) are not real-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....
Upvotes: 4