С# Секундомер против Thread.Sleep: разные прошедшие миллисекунды

Я создал таймер с паузой, используя как System.Timers.Timer, так и System.Diagnostics.Stopwatch, следующим образом:

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);
    }
}

Все казалось правильным, пока я не начал писать несколько тестовых примеров и не обнаружил, что следующий код выдает странные результаты:

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);
});

Последние 5 результатов (stopwatch.Elapsed.TotalMilliseconds):

  • 57.1516
  • 57.4105
  • 60.5827
  • 43.0608
  • 52.7358

Это огромная разница. Почему эти значения не близки к 32?

Это влияет на мои результаты класса PausableTimer.


person Yves Calaci    schedule 14.04.2021    source источник
comment
Я должен был бы проверить это. К настоящему времени мне интересно, насколько безопасно использовать PausableTimer с точки зрения точности.   -  person Yves Calaci    schedule 14.04.2021
comment
См. также соответствующие ​​stackoverflow.com/questions/15071359/ обычно невозможно сделать это очень точно, если вы не кодируете на правильном чипе реального времени и ОС, которая поддерживает то, что x86/ х64 нет.   -  person Charlieface    schedule 14.04.2021
comment
То есть в основном это PausableTimer так же неточно, как, например, System.Timers.Timer? Это оно?   -  person Yves Calaci    schedule 14.04.2021
comment
Небольшое примечание: вот как создать таймер с паузой с помощью Microsoft Reactive Framework. var duration = new Subject<TimeSpan>(); var pausableTimer = duration.Select(x => x == TimeSpan.Zero ? Observable.Never<long>() : Observable.Interval(x)).Switch();   -  person Enigmativity    schedule 14.04.2021