0

I have created an pausable timer, using both a System.Timers.Timer and a System.Diagnostics.Stopwatch, as follows:

public class PausableTimer
{
    private double interval;
    private readonly Timer timer;
    private readonly Stopwatch stopwatch;

    public event ElapsedEventHandler Tick
    {
        add => timer.Elapsed += value;
        remove => timer.Elapsed -= value;
    }

    public int Interval
    {
        get => Convert.ToInt32(timer.Interval);
        set
        {
            if (Enabled)
            {
                throw new Exception("Can't set interval while running");
            }
            else
            {
                interval = Convert.ToDouble(value);
                SetInterval(value);
            }
        }
    }

    public bool Enabled
    {
        get => timer.Enabled;
        set
        {
            if (value)
            {
                Start();
            }
            else
            {
                Stop();
            }
        }
    }

    public double Elapsed
    {
        get => stopwatch.Elapsed.TotalMilliseconds;
    }

    public PausableTimer(int milliseconds)
    {
        interval = Convert.ToInt32(milliseconds);
        stopwatch = new Stopwatch();
        timer = new Timer(interval);
        timer.AutoReset = true;
        timer.Elapsed += OnElapsed;
    }

    private void OnElapsed(object sender, ElapsedEventArgs e)
    {
        lock (this)
        {
            stopwatch.Reset();
            SetInterval(interval);
            stopwatch.Start();
        }
    }

    public void Start()
    {
        lock (this)
        {
            stopwatch.Reset();
            timer.Start();
            stopwatch.Start();
        }
    }

    public void Stop()
    {
        lock (this)
        {
            timer.Stop();
            stopwatch.Stop();
        }
    }

    public void Pause()
    {
        lock (this)
        {
            Stop();
            SetInterval(Interval - stopwatch.Elapsed.TotalMilliseconds);
        }
    }

    private void SetInterval(double value)
    {
        timer.Interval = Math.Max(value, 0.1);
    }
}

Everything seemed just right, untill I started writing a few test cases and found out that the following code reports weird results:

new TaskFactory().StartNew(() =>
{
    var stopwatch = new Stopwatch();
    var interval = 32;

    stopwatch.Start();
    Thread.Sleep(interval);
    stopwatch.Stop();
                    
    Console.WriteLine("Elapsed: {0}. Expected: {1}.", stopwatch.Elapsed.TotalMilliseconds, interval);
});

The lastest 5 results are (stopwatch.Elapsed.TotalMilliseconds):

  • 57.1516
  • 57.4105
  • 60.5827
  • 43.0608
  • 52.7358

That's a huge difference. Why aren't these values close to 32?

This is affecting my PausableTimer class results.

Yves Calaci
  • 1,019
  • 1
  • 11
  • 37
  • I would have to test it out. By now, I'm wondering how safe is to use `PausableTimer` in terms of accuracy. – Yves Calaci Apr 14 '21 at 01:19
  • See also relevant https://stackoverflow.com/questions/15071359/how-to-set-timer-resolution-from-c-sharp-to-1-ms it's not generally possible to do this very accurately unless you are coding on a proper real-time chip and an OS which supports that, which x86/x64 is not. – Charlieface Apr 14 '21 at 01:20
  • So basically this `PausableTimer` is just as inaccurate as the `System.Timers.Timer`, for example? Is that it? – Yves Calaci Apr 14 '21 at 01:23
  • Just a bit of a side note: here's how to create a pausable timer with Microsoft's Reactive Framework. `var duration = new Subject(); var pausableTimer = duration.Select(x => x == TimeSpan.Zero ? Observable.Never() : Observable.Interval(x)).Switch();` – Enigmativity Apr 14 '21 at 04:54

0 Answers0