哪种线程睡眠方法最精确:Monitor.Wait vs System.Timer vs DispatchTimer vs

本文关键字:vs Wait Monitor System DispatchTimer Timer 线程 方法 | 更新日期: 2023-09-27 18:34:12

哪个 .NET 对象(或技术)每 XXX 毫秒启动一个线程最精确? 有什么权衡?

例如:

        int maxDurationMs = 1000;
        while (true)
        {
            DateTime dt = DateTime.UtcNow;
            DoQuickStuff()
            TimeSpan duration1 =  DateTime.UtcNow - dt;
            int sleepTime = maxDurationMs - duration1.Milliseconds;
            if (sleepTime > 0)
                System.Threading.Thread.Sleep(sleepTime);
        }

       // CPU Intensive, but fairly accurate
       int maxDurationMs = 1000;
        while (true)
        {
            DateTime dt = DateTime.UtcNow;
            DoQuickStuff()
            while (true)
            {
                if (dt.AddMilliseconds(maxDurationMs) >= DateTime.UtcNow)
                    break;
            }
        }

执行相同操作的替代方法,但具有不同程度的准确性和权衡(CPU 等)

  • 系统定时器
  • 调度计时器
  • 系统线程.计时器
  • 线程.加入
  • .NET 4.0 任务
  • Thread.Sleep()
  • Monitor.Wait(obj, timespan)
  • 多媒体计时器(感谢Brian Gideon)
  • Win32 高分辨率计时器
  • 别的?

哪种线程睡眠方法最精确:Monitor.Wait vs System.Timer vs DispatchTimer vs

我自己从未真正使用过它们,但据说多媒体计时器具有 Windows 中任何定时服务中最好的分辨率。.NET BCL 尚无此定时服务的包装器,因此必须自己执行 P/Invoke 调用。

另一种选择可能是在紧密循环中将Stopwatch与一些标准Thread.Sleep调用一起使用。我不确定这种方法会有多少运气,但它可能比普通的旧Thread.Sleep调用本身更准确。我从未尝试过,但我想任何事情都值得一试。

做了一些实验,我发现将线程优先级更改为ThreadPriority.Highest会产生相当大的差异。在我尝试的每种技术上,它使间隔的标准偏差降低了相当多。

不要使用 DateTime:在大多数系统上,它的精度限制在 16 毫秒左右。(见Eric Lippert的博客)

最准确的方法是让一个专用线程运行一个 while 循环,其中包含 System.Diagnostics.Stopwatch 对象来计算时间。

即使有现存最精确和准确的计时器,考虑到 CPU 时间片的不可预测性,每 x 毫秒精确地引发一个事件也不是一件容易的事:我建议研究游戏如何进行其主循环(例如,通过延迟补偿实现稳定的 30fps)。一个很好的例子是OpenTK的GameWindow,特别是RaiseUpdateFrame方法。

如果你想要精确的间隔,Windows计时器可能不是你需要使用的,也许某种RTOS更适合你。

从上面的链接:

创建计时器 API 是为了解决当前可用计时器的问题...但是,Windows 计时器并不像应用程序可能需要的那样准确。尽管 Windows 计时器消息可以以毫秒级精度进行计划,但它们很少提供该结果,因为 Windows 计时器的准确性取决于系统时钟和当前活动。由于WM_TIMER消息以低优先级处理,有点类似于WM_PAINT消息,因此在处理其他消息时,它们通常会延迟。

在我的

窗口服务中,我使用Monitor.Wait的定期操作,因为它释放了一个线程并允许我执行操作,而不必担心在完成之前下一个"计时器滴答声"。有了这个,我得到了 +/- 1 毫秒的精度。如果一切顺利。

但是,如果您需要可以信赖的完美精度,则不应使用 .NET。实际上你不应该使用Windows。您的进程(或线程)始终有可能在执行中被推迟。

System.Timers.Timer 对象的基本实现偏差了大约 120 毫秒,导致我每分钟至少跳过一秒。

我能够使用以下技术以 1 分钟的间隔在 1 毫秒内获得准确的计时器。 较短的间隔可能无法达到相同的精度(加上DoWork()的开销在这种效率中起作用)

 public class SystemTimerTest
   {
    readonly System.Timers.Timer timerRecalcStatistics;
    readonly System.Diagnostics.Stopwatch stopwatchForRecalcStatistics = new System.Diagnostics.Stopwatch();

    public SystemTimerTest(TimeSpan range, DataOverwriteAction action)
    {
        int recalculateStatisticsEveryXMillseconds = 1000;
        timerRecalcStatistics = new System.Timers.Timer(recalculateStatisticsEveryXMillseconds);
        timerRecalcStatistics.AutoReset = true;
        timerRecalcStatistics.Elapsed += new System.Timers.ElapsedEventHandler(TimerRecalcStatisticsElapsed);
        timerRecalcStatistics.Interval = recalculateStatisticsEveryXMillseconds;
        timerRecalcStatistics.Enabled = true;

        this.maxRange = range;
        this.hashRunningTotalDB = new HashRunningTotalDB(action);
        this.hashesByDate = new HashesByDate(action);
        this.dataOverwriteAction = action;
    }

    private void TimerRecalcStatisticsElapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        stopwatchForRecalcStatistics.Start();
        Console.WriteLine("The TimerRecalcStatisticsElapsed event was raised at {0}", e.SignalTime.ToString("o"));
         // DO WORK HERE

        stopwatchForRecalcStatistics.Stop();
        double timeBuffer  = GetInterval(IntervalTypeEnum.NearestSecond, e.SignalTime) - stopwatchForRecalcStatistics.ElapsedMilliseconds;
        if (timeBuffer > 0)
            timerRecalcStatistics.Interval = timeBuffer;
        else
            timerRecalcStatistics.Interval = 1;
        stopwatchForRecalcStatistics.Reset();         
        timerRecalcStatistics.Enabled = true;
    }
 }

我想知道这是否意味着每 1 秒周期损失 1 到 120 毫秒意味着 CPU 的效率不如此实现所能达到的效果。