9

我搜索了这个问题,但没有看到答案。如果它是重复的,我很乐意将其关闭。

我目前正在尝试对一项技术进行一些性能评估,并看到了一些令人难以置信的结果,因此我决定进行一些实验。在那我想尝试看看 Stopwatch 类是否返回了我所期望的。

Stopwatch sw = Stopwatch.StartNew();
Thread.Sleep(1);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

在这种情况下,我几乎看到了 15 毫秒的返回值。我知道 DateTime 的分辨率在那里,但 Thread.Sleep(1) 不应该让线程休眠 1 毫秒吗?我所在的系统返回 Stopwatch.IsHighResolution true 并在 .NET 4 中运行。

背景:此代码以其完整和正确的形式旨在收集 Aerospike db get 请求中的一些数字。数据库不在同一个盒子上。当我在查询中间打印出 sw.ElapsedMilliseconds 时,我看到的大多是亚毫秒级响应,考虑到我的 Java 等效代码大部分时间返回更可信的 5ms-15ms 响应,这听起来有点可疑。Java 代码使用 System.nanoTime() 的差异。我的 C# 代码中的 submilli 响应是指 Console.WriteLine(sw.ElapsedMilliseconds) 打印 0。

4

6 回答 6

34

秒表以外的定时器由时钟中断递增。默认情况下,在 Windows 上每秒滴答 64 次。或 15.625 毫秒。所以一个小于 16 的 Thread.Sleep() 参数不会给你你正在寻找的延迟,你总是会得到至少 15.625 的间隔。同样,如果您读取 Environment.TickCount 或 DateTime.Now 并等待不到16 毫秒,那么您将读取相同的值并认为 0 毫秒已经过去。

始终使用秒表进行小增量测量,它使用不同的频率源。它的分辨率是可变的,它取决于主板上的芯片组。但是你可以相信它比微秒更好。Stopwatch.Frequency 为您提供速率。

可以更改时钟中断率,您必须调用 timeBeginPeriod()。这可以让你降低到一毫秒,实际上使 Thread.Sleep(1) 准确。最好不要这样做,它对电源很不友好。

于 2013-09-28T12:33:28.170 回答
5

我希望这Thread.Sleep只不过是一个围绕 Win32 SleepAPI 的托管包装器。众所周知,这个 API 函数的分辨率很低。文档是这样说的:

此函数使线程放弃其时间片的剩余部分,并在基于 dwMilliseconds 值的时间间隔内变得不可运行。系统时钟以恒定速率“滴答”。如果 dwMilliseconds 小于系统时钟的分辨率,则线程可能休眠的时间少于指定的时间长度。如果 dwMilliseconds 大于 1 个滴答但小于 2 个滴答,则等待可能介于 1 到 2 个滴答之间,依此类推。要提高睡眠间隔的准确性,请调用 timeGetDevCaps 函数以确定支持的最小计时器分辨率,并调用 timeBeginPeriod 函数将计时器分辨率设置为最小值。调用 timeBeginPeriod 时要小心,因为频繁调用会显着影响系统时钟、系统电源使用和调度程序。如果你调用 timeBeginPeriod,

休眠间隔过后,线程就可以运行了。如果您指定 0 毫秒,线程将放弃其时间片的剩余部分但保持就绪状态。请注意,不保证就绪线程会立即运行。因此,线程可能要等到休眠间隔过去一段时间后才会运行。有关详细信息,请参阅计划优先级。

因此,您可以按照建议使用timeBeginPeriodandtimeEndPeriod来增加计时器分辨率,但实际上不建议这样做。如果您确实需要在短时间内阻止,那么我建议您找到更好的解决方案。例如多媒体定时器。

于 2013-09-28T12:31:51.600 回答
2

是的,您发现的绝对正确。

Thread.Sleep不保证在N毫秒内释放。它将确保Sleep不会在N几毫秒之前释放。

换句话说Thread.Sleep(1),意味着睡眠至少 1 毫秒。操作系统不会为特定线程安排特定时间的时间片。

该线程将不会被操作系统安排在指定的时间内执行。此方法将线程的状态更改为包含 WaitSleepJoin。

来自Msdn

于 2013-09-28T12:29:43.870 回答
1

Thread.Sleep(n) 意味着阻塞当前线程至少在 n 毫秒内发生的时间片(或线程量子)的数量。时间片的长度在不同版本/类型的 Windows 和不同的处理器上是不同的,一般在 15 到 30 毫秒之间。这意味着线程几乎可以保证阻塞超过 n 毫秒。您的线程恰好在 n 毫秒后重新唤醒的可能性几乎是不可能的。因此, Thread.Sleep 对计时毫无意义。

于 2013-09-28T12:29:14.613 回答
1

搜索“线程睡眠解决方案”会提出这些相关的 SO 问题:

共识是,默认情况下,Sleep() 至少需要 15 毫秒,除非您使用 timeBegin/EndPeriod() 设置分辨率,如本答案所示。

于 2013-09-28T12:33:08.133 回答
0

Thread.Sleep(1)将使线程休眠 1ms,但通常无法获得该精度。

但是,一旦线程进入睡眠状态,内核需要时间将其放回就绪队列,然后在线程真正再次运行时将其放入运行状态。

所有这些因素都取决于平台、CPU 的数量、其他进程的数量等。对于非 RT 操作系统,您无法对此得到任何保证。

于 2013-09-28T12:29:32.760 回答