9

Thread.Sleep() 分辨率从 1 到 15.6 毫秒不等

鉴于此控制台应用程序:

class Program
{
    static void Main()
    {
        int outer = 100;
        int inner = 100;
        Stopwatch sw = new Stopwatch();

        for (int j = 0; j < outer; j++)
        {
            int i;
            sw.Restart();
            for (i = 0; i < inner; i++)
                Thread.Sleep(1);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }
}

我预计输出是接近 100 的 100 个数字。相反,我得到了这样的结果:

99 99 99 100 99 99 99 106 106 99 99 99 100 100 99 99 99 99 101 99 99 99 99 99 101 99 99 99 99 101 99 99 99 100 99 99 99 99 99 103 99 99 99 99 100 99 99 99 99 813 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1559 1560 1559 1559 1559 1559 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1558 1559 1558 1558 1558 1558 1558

但有时我不会得到任何准确的结果;每次都是〜1559。

为什么不一致?

一些谷歌搜索告诉我 15.6 毫秒是时间片的长度,这解释了 ~1559 的结果。但为什么有时我会得到正确的结果,而有时我只是得到 15.6 的倍数?(例如 Thread.Sleep(20) 通常会给出 ~31.2ms)

它如何受硬件或软件的影响?

我之所以这样问,是因为是什么让我发现了它:

我一直在 32 位双核机器上开发我的应用程序。今天我的机器升级到 64 位四核并重新安装了完整的操作系统。(Windows 7 和 .NET 4 在这两种情况下,但我不能确定旧机器有 W7 SP1;新机器有。)

在新机器上运行我的应用程序后,我立即注意到我的表单需要更长的时间才能淡出。我有一个自定义方法来淡化我的表单,它使用 Thread.Sleep() 值从 10 到 50 不等。在旧系统上,这似乎每次都能完美运行。在新系统上,褪色所需的时间比应有的要长得多。

为什么我的旧系统和新系统之间的这种行为会发生变化?这与硬件或软件有关吗?

我可以让它始终准确吗?(~1ms 分辨率)

我可以在我的程序中做些什么来使 Thread.Sleep() 可靠地精确到大约 1 毫秒吗?甚至10毫秒?

4

5 回答 5

7

答案是不使用Thread.Sleep,而是使用高分辨率计时器。您需要在繁忙的循环中进行淡入淡出,但这听起来没问题。您根本无法期望获得高分辨率,Thread.Sleep并且因在不同硬件上的表现不同而臭名昭著。

Stopwatch如果硬件支持高分辨率性能计数器,您可以使用 .net 上的类。

于 2011-09-30T19:09:38.097 回答
7

“Sleep 函数至少在指定的时间间隔内暂停当前线程的执行。”</p>

-> http://social.msdn.microsoft.com/Forums/en/clr/thread/facc2b57-9a27-4049-bb32-ef093fbf4c29

于 2011-09-30T19:11:21.523 回答
3

我可以回答我的一个问题:我可以让它始终准确吗?(~1ms 分辨率)

是的,看来我可以,使用timeBeginPeriod()和 timeEndPeriod()。

我已经对此进行了测试,并且可以正常工作。

我读过的一些内容表明,在应用程序期间调用 timeBeginPeriod(1) 是一个坏主意。但是,在短方法开始时调用它,然后在方法结束时使用 timeEndPeriod() 清除它应该没问题。

尽管如此,我也会研究使用计时器。

于 2011-09-30T19:40:30.320 回答
3

您的机器上正在运行一个程序,该程序正在调用 timeBeginPeriod() 和 timeEndPeriod()。通常是使用 timeSetEvent() 设置一毫秒计时器的媒体相关程序。它也会影响 Sleep() 的分辨率。

您可以自己调用这些函数以获得一致的行为。但是对于 UI 效果来说并不是很合理。这对笔记本电脑的电池寿命相当不友好。

睡眠 20 毫秒并实际获得 2/64 秒是合乎逻辑的,cpu 根本不会很快醒来以注意到 20 毫秒已经过去。你只能得到 1/64 秒的倍数。因此,实现淡入淡出效果的 Timer 的合理选择是 15 毫秒,在最坏的情况下为您提供 64 fps 的动画。假设你的效果画得足够快。如果 timeBeginPeriod 被调用但不是很多,你会有点偏离。从时钟计算动画阶段也可以,但在我的书中有点矫枉过正。

于 2011-09-30T20:49:06.070 回答
-2

你的方法是错误的。你永远不会让睡眠变得准确,而且你的机器越忙,你的睡眠循环就会变得越错误。

您应该做的是查看已经过去了多少时间并相应地进行调整。尽管在 Sleep 周围旋转是一个坏主意,但“更好”的方法会更加复杂。我会保持我建议的解决方案简单。

  • 调用 DateTime.Now.Ticks 并将其保存在变量 (startTick) 中。
  • 在您的循环中,按照您已经在做的那样调用 Sleep。
  • 再次调用 DateTime.Now.Ticks 并从中减去 startTick - 该值将是自您开始淡入淡出 (timeSinceStart) 以来经过了多少 100 纳秒单位的时间。
  • 使用 timeSinceStart 计算经过这么多时间你应该褪色多少。
  • 重复直到你完全褪色。
于 2011-09-30T19:52:18.377 回答