C#计时器分辨率:Linux(单声道,dotnet核心)与Windows [英] C# Timer resolution: Linux (mono, dotnet core) vs Windows

查看:87
本文介绍了C#计时器分辨率:Linux(单声道,dotnet核心)与Windows的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need a timer that fires every 25ms. I've been comparing the default Timer implementation between Windows 10 and Linux (Ubuntu Server 16.10 and 12.04) on both the dotnet core runtime and the latest mono-runtime.

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

There are some differences in the timer precision that I don't quite understand.

我正在使用以下代码来测试计时器:

I'm using the following piece of code to test the 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上到处都是

On windows it's all over the place:

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

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

Using dotnet core on linux, it's less all over the place:

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

但是单声道Timer非常精确:

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

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

Even on windows, mono still maintains its timing precision:

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

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

What is causing this difference? Is there a benefit to the way the dotnet core runtime does things compared to mono, that justifies the lost precision?

推荐答案

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

Unfortunately you cannot rely on timers in the .NET framework. The best one has 15 ms frequency even if you want to trigger it in every millisecond. But you can implement a high-resolution timer with microsec precision, too.

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

Note: This works only when Stopwatch.IsHighResolution returns true. In Windows this is true starting with Windows XP; however, I did not test other frameworks.

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

这篇关于C#计时器分辨率:Linux(单声道,dotnet核心)与Windows的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆