繁体   English   中英

C#计时器分辨率:Linux(单点,dotnet核心)与Windows

[英]C# Timer resolution: Linux (mono, dotnet core) vs Windows

我需要一个每25毫秒触发一次的计时器。 我一直在比较dotnet核心运行时和最新的单运行时在Windows 10和Linux(Ubuntu Server 16.10和12.04)之间的默认Timer实现。

我不太了解计时器精度方面的一些差异。

我正在使用以下代码来测试Timer:

// inside Main()
        var s = new Stopwatch();
        var offsets = new List<long>();

        const int interval = 25;
        using (var t = new Timer((obj) =>
        {
            offsets.Add(s.ElapsedMilliseconds);
            s.Restart();
        }, null, 0, interval))
        {
            s.Start();
            Thread.Sleep(5000);
        }

        foreach(var n in offsets)
        {
            Console.WriteLine(n);
        }

        Console.WriteLine(offsets.Average(n => Math.Abs(interval - n)));

在Windows上到处都是:

...
36
25
36
26
36
5,8875 # <-- average timing error

在Linux上使用dotnet core,到处都是:

...
25
30
27
28
27
2.59776536312849 # <-- average timing error

但是mono Timer非常精确:

...
25
25
24
25
25
25
0.33 # <-- average timing error

编辑:即使在Windows上,单声道仍保持其定时精度:

...
25
25
25
25
25
25
25
24
0.31

是什么造成了这种差异? 与mono相比,dotnet核心运行时做事的方式是否有好处,可以证明丧失精度?

不幸的是,您不能依赖.NET框架中的计时器。 最好的频率为15毫秒,即使您想每毫秒触发一次。 但是,您也可以实现具有微秒精度的高分辨率计时器。

注意:仅当Stopwatch.IsHighResolution返回true时,此方法才有效。 在Windows中,从Windows XP开始是正确的。 但是,我没有测试其他框架。

public class HiResTimer
{
    // The number of ticks per one millisecond.
    private static readonly float tickFrequency = 1000f / Stopwatch.Frequency;

    public event EventHandler<HiResTimerElapsedEventArgs> Elapsed;

    private volatile float interval;
    private volatile bool isRunning;

    public HiResTimer() : this(1f)
    {
    }

    public HiResTimer(float interval)
    {
        if (interval < 0f || Single.IsNaN(interval))
            throw new ArgumentOutOfRangeException(nameof(interval));
        this.interval = interval;
    }

    // The interval in milliseconds. Fractions are allowed so 0.001 is one microsecond.
    public float Interval
    {
        get { return interval; }
        set
        {
            if (value < 0f || Single.IsNaN(value))
                throw new ArgumentOutOfRangeException(nameof(value));
            interval = value;
        }
    }

    public bool Enabled
    {
        set
        {
            if (value)
                Start();
            else
                Stop();
        }
        get { return isRunning; }
    }

    public void Start()
    {
        if (isRunning)
            return;

        isRunning = true;
        Thread thread = new Thread(ExecuteTimer);
        thread.Priority = ThreadPriority.Highest;
        thread.Start();
    }

    public void Stop()
    {
        isRunning = false;
    }

    private void ExecuteTimer()
    {
        float nextTrigger = 0f;

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        while (isRunning)
        {
            nextTrigger += interval;
            float elapsed;

            while (true)
            {
                elapsed = ElapsedHiRes(stopwatch);
                float diff = nextTrigger - elapsed;
                if (diff <= 0f)
                    break;

                if (diff < 1f)
                    Thread.SpinWait(10);
                else if (diff < 5f)
                    Thread.SpinWait(100);
                else if (diff < 15f)
                    Thread.Sleep(1);
                else
                    Thread.Sleep(10);

                if (!isRunning)
                    return;
            }


            float delay = elapsed - nextTrigger;
            Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay));

            // restarting the timer in every hour to prevent precision problems
            if (stopwatch.Elapsed.TotalHours >= 1d)
            {
                stopwatch.Restart();
                nextTrigger = 0f;
            }
        }

        stopwatch.Stop();
    }

    private static float ElapsedHiRes(Stopwatch stopwatch)
    {
        return stopwatch.ElapsedTicks * tickFrequency;
    }
}

public class HiResTimerElapsedEventArgs : EventArgs
{
    public float Delay { get; }

    internal HiResTimerElapsedEventArgs(float delay)
    {
        Delay = delay;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM