为什么 Thread.Sleep 在其他应用运行时等待的时间比请求的时间长

本文关键字:时间 请求 等待 运行时 Sleep Thread 其他 应用 为什么 | 更新日期: 2023-09-27 18:35:52

我在 C# 中有一个关于线程的小问题。出于某种原因,当我打开 Chrome 时,我的线程从 32 毫秒延迟加速到 16 毫秒延迟,当我关闭 Chrome 时,它会回到 32 毫秒。我正在使用Thread.Sleep(1000 / 60)来延迟。有人可以解释为什么会发生这种情况,并可能提出一个可能的解决方案吗?

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading;
 namespace ConsoleApplication2
 {
     class Program
     {
         static bool alive;
         static Thread thread;
         static DateTime last;
         static void Main(string[] args)
         {
             alive = true;
             thread = new Thread(new ThreadStart(Loop));
             thread.Start();
             Console.ReadKey();
         }
         static void Loop()
         {
             last = DateTime.Now;
             while (alive)
             {
                 DateTime current = DateTime.Now;
                 TimeSpan span = current - last;
                 last = current;
                 Console.WriteLine("{0}ms", span.Milliseconds);
                 Thread.Sleep(1000 / 60);
             }
         }
     }
 }

为什么 Thread.Sleep 在其他应用运行时等待的时间比请求的时间长

只是一个帖子来确认马修的正确答案。 Thread.Sleep() 的准确性受 Windows 上的时钟中断率的影响。 默认情况下,它每秒滴答 64 次,每 15.625 毫秒一次。 Sleep() 只有在发生此类中断时才能完成。 这里的心理图像是由"睡眠"这个词引起的,处理器实际上是睡眠状态并且不执行代码。 只有该时钟中断才会再次唤醒它以继续执行代码。

你选择1000/60是一个非常不愉快的选择,要求16毫秒。 略高于 15.625,因此您总是会在至少 2 个刻度后醒来:2 x 15.625 = 31 毫秒。 你测量了什么。

然而,该中断率不是固定的,可以通过程序进行更改。 它通过调用 CreateTimerQueueTimer() 或 legacy timeBeginPeriod() 来实现。 浏览器通常需要这样做。 像制作 GIF 动画这样简单的事情需要更好的计时器,因为 GIF 帧时间是以 10 毫秒为单位指定的。 或者一般来说,任何与多媒体相关的操作都需要它。

程序这样做的一个非常丑陋的副作用是,这种增加的时钟中断率具有系统范围的影响。 就像在你的程序中所做的那样。 你的计时器突然变得准确,你实际上得到了你要求的睡眠时间,16毫秒。 因此,Chrome 正在将速率更改为每秒 1000 个刻度。 支持的最大值。 当您拥有竞争的操作系统时,对业务有好处。

您可以通过选择与默认中断率更接近的睡眠持续时间来避免此问题。 如果你要求15,那么你会得到15.625,Chrome不能对此产生影响。 31 是下一个最佳点。 等等,15.625 的整数倍并向下舍入。

更新:请注意,此行为最近发生了变化。 从 Win10 版本 2004 开始,效果不再是全局的,因此 Chrome 无法再影响您的程序。从 Win11 开始,具有非活动窗口的应用以默认中断速率运行。

这可能是因为Chrome(或Chrome的某些组件)调用timeBeginPeriod()的值增加了Windows API函数Sleep()的分辨率,该函数是从Thread.Sleep()调用的。

有关更多信息,请参阅此线程:我可以提高 Thread.Sleep 的分辨率吗?

几年前,我在Windows Media Player中注意到了这种行为:我们的一个应用程序的行为根据Windows Media Player是否正在运行而更改。原来,WMP在打电话给timeBeginPeriod().

然而,一般来说,Thread.Sleep()(以及Windows API Sleep())是非常不准确的。

首先,1000/60 = 16 ms

PC时钟的分辨率约为18-20ms,Sleep()并且DateTime.Now的结果将四舍五入为该值的倍数。

因此,Thread.Sleep(5)Thread.Sleep(15)将延迟相同的时间。这可能是 20、40 甚至 60 毫秒。你没有得到太多的保证,Sleep()的论据只是最低限度的。

另一个占用 CPU(甚至一点点)的进程 (Chrome) 可以通过这种方式影响程序的行为。 编辑:这与你所看到的相反,所以这里正在发生一些其他事情。不过,这是关于四舍五入到时间片。

基本上,Thread.Sleep不是很准确。

Thread.Sleep(1000/60)(计算结果为 Thread.Sleep(16) ),要求线程进入睡眠状态,并在 16 毫秒后返回。但是,在经过更长的时间之前,该线程可能不会再次执行;例如,32ms。

至于为什么Chrome会产生影响,我不知道,

但是由于Chrome为每个选项卡生成一个新线程,因此它将对系统的线程行为产生影响。

您在使用 DateTime 时遇到解决方案问题。 您应该使用Stopwatch来实现这种精度。 Eric Lippert指出,DateTime只能精确到30毫秒左右,因此在这种情况下,您的读数不会告诉您任何事情。

测量是问题的一半。 循环的实际时间变化是由于Sleep分辨率(如其他答案中所述)。