Thread.Sleep(TimeSpan)的精度有多高?

21

我遇到了一个单元测试,它会间歇性地失败,因为经过的时间不是我预期的。

这个测试的例子如下:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

TimeSpan oneSecond = new TimeSpan(0, 0, 1);

for(int i=0; i<3; i++)
{
    Thread.Sleep(oneSecond);
}

stopwatch.Stop();

Assert.GreaterOrEqual(stopwatch.ElapsedMilliseconds, 2999);
大部分时间这都能通过测试,但至少有一次失败了,原因是:
期望值:大于等于2999 实际值:2998
我不明白它怎么可能少于3秒。Thread.Sleep或者Stopwatch是否存在精度问题,还是我没有意识到什么呢?
更新一些在下面的问题。正在进行单元测试的场景是允许调用一个方法执行某个操作,如果失败等待一秒钟并重新调用该方法的类。上面展示的测试只是对正在发生的事情的近似描述。
假设我想调用DoSomething()方法...但是如果DoSomething()抛出异常,我想能够最多重试3次,但在每次尝试之间等待1秒钟。在这种情况下,这个单元测试的目的是验证当我们请求3次,在每次重试之间等待1秒钟时,所需总时间大于3秒。

只是出于好奇,为什么你要编写一个测试Thread.Sleep()的单元测试呢?我猜想你实际上是在测试某个活动是否能在3秒内完成,但为什么不直接调用Thread.Sleep(3000)呢? - MusiGenesis
@MusiGenesis 我更新了问题,试图澄清一些事情。 - mezoid
仅供参考。自从我发现了这个小代码,我在任何地方都使用它:http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx - sabiland
6个回答

20

您的线程正在与其他线程共享CPU时间。休眠将在您再次获得轮次并且内核注意到已经过了休眠时间时结束,因此它不是非常准确。

CPU负载、进程优先级、并发线程数,甚至来自其他进程的线程都会对其产生影响。


6
如果内核注意到休眠时间已经过去,就结束了休眠,那么它怎么可能比我预期的时间少呢?在我的情况下,3010毫秒是可以理解和接受的,但它在达到所请求的时间之前就停止了……尽管只差1-2毫秒。 - mezoid
3
实现可能会在您指定的时间之前设置几毫秒的唤醒时间。如果不这样做,它将始终比您指定的时间更晚唤醒。通过提前设置时间,它更有可能接近您指定的时间。 - Spencer Ruport
@mezoid:我认为PC架构并不适合具有很高的时间精度。旧PC每秒仅“滴答”18.2次,因此时钟和“滴答计数”只能每52毫秒更新一次。我相信现在肯定要快得多,但我仍然不确定它有多准确。 - Havenard
那么它通常准确吗?在长时间的睡眠间隔中可能会有几分钟的偏差吗? - jjxtra
在我的机器上,大约在10毫秒左右(具体数字我记不清了),如果你真的想要的话,你可以提前安排并旋转CPU直到达到你的目标。 - Bas Smit

8

Thread.Sleep并不适用于精确唤醒,实际上,Windows架构本身也不是为这种情况而设计的。


4
快速尝试后,我发现像这样的代码片段...
do { Debug.WriteLine( DateTime.Now.TimeOfDay.TotalMilliseconds.ToString() ); } while ( 1 );
会显示多次相同的数字,然后跳到新的多次显示的数字等等。这些数字集之间的间隔始终为15.625毫秒,我注意到这是1000/64。
看起来Windows计时器的粒度为1/64秒。如果您需要更好的精度,则我感到遗憾,但这是您必须适应的框架。(Windows不是硬实时操作系统,也没有声称是)。

2
线程睡眠和时间/节流是非常不同的事情,应该适当处理。线程睡眠是一项通用任务,允许系统在没有具体说明的情况下给其他线程和进程执行的机会。另一方面,限制应用程序或安排需要准确计时的任务应该使用显式计时器进行。
请记住,如果您需要时间精确的进程或同步,您将很难通过Windows中的普通进程实现。您需要利用Windows实时优先级才能成功实现准确的计时或限制,因为Windows可以在任何时候通过另一个线程抢占来使任何线程睡眠。

我同意,Thread.Sleep 的目的是关于线程优先级,而不是与精确时钟定时有关。直到我们能够在个人电脑中拥有铯加工时钟,不幸的是绝对精确的时间将不可用。 :Phttp://en.wikipedia.org/wiki/Caesium - Russell
嗯,我希望大多数人不需要原子钟般精确的时间。:P 我认为大多数CPU中的时钟对于除了测量原子振动之外的绝大多数应用程序来说已经足够精确了。 :D - jrista

1
在一个应用程序中,我想要至少睡眠x毫秒,在这种情况下,我使用了类似于以下代码的一些代码:
public void Sleep(int milliseconds)
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    while (stopwatch.ElapsedMilliseconds < milliseconds)
    {
        int timeout = milliseconds - stopwatch.ElapsedMilliseconds;
        Thread.Sleep(timeout >= 0 ? timeout : 0);
    }

    stopwatch.Stop();
}

关于Thread.Sleep的准确性,它根本不准确。我认为分辨率大约在10毫秒左右。它不能保证做任何事情,除了“大约”这么长时间。

你能指给我任何关于分辨率的参考资料吗?如果它确实是10毫秒,我可以将2999更改为2990..... - mezoid
1
由于其他人指出的因素(例如处理器负载的影响),没有硬性和快速的解决方案。基本上,这取决于您的线程何时再次获得优先级以开始执行。然而,在 Stack Overflow 的几个地方看到的一些轶事证据指出,平均值约为 10 毫秒。 - Matthew Scharley
以下是关于编程的内容,翻译成中文。请返回翻译后的文本:https://dev59.com/uXNA5IYBdhLWcg3wC5JR#1116297 以及其他我曾经阅读过但现在找不到的答案。 - Matthew Scharley

-1
也许你不应该依赖时间偏差来判断是否成功。最好计算尝试次数并根据此进行评估:
int tries;

for(tries=0; tries<3; tries++)
{
    Thread.Sleep(oneSecond);
}

Assert.GreaterOrEqual(tries, 3);

3
这将测试什么?是for循环正常工作吗? - Tvde1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接