为什么 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() 的准确性受 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为每个选项卡生成一个新线程,因此它将对系统的线程行为产生影响。
您在使用 DateTime
时遇到解决方案问题。 您应该使用Stopwatch
来实现这种精度。 Eric Lippert指出,DateTime
只能精确到30毫秒左右,因此在这种情况下,您的读数不会告诉您任何事情。
测量是问题的一半。 循环的实际时间变化是由于Sleep
分辨率(如其他答案中所述)。