【问题标题】:What Thread sleep method is most precise: Monitor.Wait vs System.Timer vs DispatchTimer vs Threading.Timer哪种线程睡眠方法最精确:Monitor.Wait vs System.Timer vs DispatcherTimer vs Threading.Timer
【发布时间】:2012-05-23 11:52:50
【问题描述】:

什么 .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等)

【问题讨论】:

  • 我敢肯定,某种计时器可以完成这项工作; stackoverflow.com/questions/1416803/…。无法保证它会在 精确 N 毫秒内触发,但它们会很接近。
  • 精确吗?精确到什么程度?除非您可以限制可接受的抖动,否则根本无法满足您的要求。
  • @MartinJames Precise = 在interval * 3 内,或者在这种情况下为 4.5 秒,尽管我想做得更好(并且认为我什至可以使用 thread.sleep)

标签: c# .net multithreading timer threadpool


【解决方案1】:

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

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

我做了一些实验,发现将线程优先级更改为ThreadPriority.Highest 有很大的不同。在我尝试的每种技术中,它都将间隔的标准偏差降低了很多。

【讨论】:

【解决方案2】:

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

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

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

【讨论】:

  • 是的,我做了一些实验,在紧密循环中使用Stopwatch 似乎效果最好,尤其是当您将线程设置为最高优先级时。
【解决方案3】:

如果您想要 precise intervals Windows Timers 可能不是您需要使用的,可能某种 RTOS 会更适合您。

从上面的链接:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • +1 - 我当然同意,对于需要任何类型的间隔或等待的每项任务“使用计时器”的几乎普遍建议通常是一种非常不合适的方式。
    • 我真的怀疑你会得到 1 毫秒的精度,因为线程时间片大约是 15 毫秒。
    【解决方案5】:

    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 的效率不如此实现所能达到的效率。

    【讨论】:

      猜你喜欢
      • 2012-07-08
      • 2017-07-02
      • 2014-01-31
      • 1970-01-01
      • 2012-01-10
      • 1970-01-01
      • 1970-01-01
      • 2019-12-21
      • 2014-05-14
      相关资源
      最近更新 更多