Reputation: 2481
I'm trying to develop simple timer which can save its last value and continue from it at the new app start.
Stopwatch
class is not serializable and even cannot be initialized in order to be started from specific time. But it works great. Benchmark showed that stopwatch's 1 minute is really 1 minute.
I tried to use TimeSpan
in the following way:
private TimeSpan timerNew = new TimeSpan();
private DispatcherTimer dispatcherTimer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Tick += Timer_Tick;
}
private void Timer_Tick(object sender, EventArgs e)
{
timerNew += new TimeSpan(0, 0, 0, 1);
TbTimer.Text = String.Format("{0:00}:{1:00}:{2:00}",
timerNew.Hours, timerNew.Minutes, timerNew.Seconds);
}
private void ButtonStart_OnClick(object sender, RoutedEventArgs e)
{
dispatcherTimer.Start();
}
private void ButtonStop_OnClick(object sender, RoutedEventArgs e)
{
dispatcherTimer.Stop();
}
private void ButtonReset_OnClick(object sender, RoutedEventArgs e)
{
timerNew = new TimeSpan();
TbTimer.Text = "00:00:00";
}
When I checked it against real stopwatch, I found out that this timer implementation lost 2 seconds per minute.
I also tried my own Timer implementation which is simple class with ulong
field, which is incremented on each dispatcherTimer tick. And UI shows results after transformation of seconds to hours, minutes and so on. But it also loses 2 seconds per minute comparing to real stopwatch.
Why these 2 seconds are lost? What is an alternative to Stopwatch
for usage in a customizable timer?
Upvotes: 2
Views: 561
Reputation: 70652
The Windows thread scheduler is not a "real-time" scheduler, as Windows is not a "real-time OS". In other words, all timing and scheduling is done on a "best effort" basis, without any guarantee of exact precision. In addition, this always results in lost time, because the one guarantee you do have is that scheduling will not happen early. So when there's an imprecision, it's always in the direction of "late".
The Stopwatch
class works because it uses CPU-supported performance counters, which doesn't rely on the OS scheduler. The hardware itself tracks the elapsed time and provides the information you need.
I recommend against the use of DateTime.UtcNow
for measuring elapsed time, for two reasons: first, the clock DateTime
uses is adjustable, and so even using UTC time (which at least would compensate for automatic adjustments due to Daylight Saving Time) is not guaranteed to be accurate. Second, your specific scenario seems to involve an issue where you want to serialize the current state and restore it, which DateTime.UtcNow
doesn't address anyway.
Instead, you should make your own serializable stopwatch class, which uses Stopwatch
itself as the basis, but which stores a base elapsed value that you add to the Stopwatch
's elapsed value.
For example:
class SerializableStopwatch
{
public TimeSpan BaseElapsed { get; set; }
public TimeSpan Elapsed { get { return _stopwatch.Elapsed + BaseElapsed; } }
private Stopwatch _stopwatch = new Stopwatch();
// add whatever other members you want/need from the Stopwatch class,
// simply delegating the operation to the _stopwatch member. For example:
public void Start() { _stopwatch.Start(); }
public void Stop() { _stopwatch.Stop(); }
// etc.
}
How exactly you would serialize the above is up to you. In the simplest scenario, you can just format the Elapsed
property as a string to save the value, and then when you want to restore the object, parse that value, create a new instance of the above class, and then assign the value to the BaseElapsed
property.
For additional discussion on the topic, you might find Eric Lippert's blog article Precision and accuracy of DateTime useful and interesting.
Upvotes: 3