Dragon
Dragon

Reputation: 2481

Timer with TimeSpan and self-made timer are slower than Stopwatch

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

Answers (1)

Peter Duniho
Peter Duniho

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

Related Questions