在IT技术中,Thread.Sleep如何让线程休眠少于1毫秒?

41

我想调用少于1毫秒的线程休眠。 我读到既不支持 thread.Sleep,也不支持Windows-OS。

有什么解决办法吗?

对于所有好奇为什么我需要这个的人: 我正在进行压力测试,并想知道我的模块每秒可以处理多少条消息。 所以我的代码是:

 // Set the relative part of Second hat will be allocated for each message 
 //For example: 5 messages - every message will get 200 miliseconds 
 var quantum = 1000 / numOfMessages;

 for (var i = 0; i < numOfMessages; i++)
 {
      _bus.Publish(new MyMessage());
      if (rate != 0) 
          Thread.Sleep(quantum);
 }

我很乐意听取你对此的意见。


8
为什么你想要这样做?解决方案可能是采用更合适的方式来处理你的问题。;-) - Achim
3
如果操作系统不支持,那么不能这样做。你为什么想这样做? - zellio
1
如果我问一下,这是什么目的? - evilone
1
你不能这样做;Thread.Sleep 几乎总是会使你的线程暂停执行,而你无法控制或预测操作系统何时再次安排它。在非实时操作系统上精确休眠特定时间是不可能的。 - Sven
6个回答

53

你不能这样做。单个睡眠调用通常会阻塞比1毫秒更长的时间(它取决于操作系统和系统,但根据我的经验,Thread.Sleep(1)通常会阻塞12-15ms之间)。

一般来说,Windows不是设计为实时操作系统。在普通的(桌面/服务器)Windows版本上,通常不可能实现这种控制。

你通常能够得到最接近的方法就是旋转并消耗CPU周期,直到达到想要的等待时间(使用高性能计数器测量)。然而,这非常糟糕 - 你将占用一个完整的CPU,即使这样,你也可能会受到操作系统的抢占,效果上相当于"睡眠"的时间比1毫秒更长...


12
对于来到这个线程的其他人,我已经使用 Mono C# 在 Linux/Ubuntu 下进行了测试,使用 Thread.Sleep(1) 可以精确地让线程休眠 1 毫秒。 - JaredBroad
2
Vista及更高版本支持无滴答调度。如果硬件支持此功能,内核可以进行1ms以下的调度,但睡眠时间仅限于1ms。请记住,睡眠只保证至少睡眠该时间,但线程何时唤醒没有任何保证。 - Bengie
2
我讨厌这个答案。从睡眠的角度来看,它在技术上是正确的,但就OP和大多数人想要的而言,Sam提供了一种蛮力但非常有效的解决方案。这个答案得到了很高的赞同和坚定的“你不能这样做”的回答,这会阻止人们向下滚动并找到他们的解决方案。 - jalalipop
1
@jalalipop 整个最后一段都在描述下面提到的技术,正如我所说,它并不一定能正常工作(因为你可能会被抢占),但它确实允许更精确的控制。话虽如此,如果你正在做这件事情,SpinWait是比下面的方法更好的选择—请参见 https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.spinwait?view=netframework-4.8 - Reed Copsey
最近我解决了这个问题(有点),只需旋转直到Stopwatch.ElapsedTicks达到所需的时间即可。它的效果出奇地好,我用它来模拟异步数据提供程序。1毫秒比我的典型网络下载慢,因此我设置了自己的WaitAsync(),允许指定像0.1毫秒这样的时间。现在它的行为更像真正的下载,即使速度不规则;)如果您想要精确的延迟,那么它几乎没有用处,但是当+/- 50%可以接受时,这就是完美的。 - Harry
显示剩余3条评论

31

与调用Thread.Sleep(x);(尽管该方法将阻塞线程,而不是将其放入睡眠状态)相比,下面的代码将提供一种更精确的阻止方式。我们在下面使用StopWatch类来测量需要保持循环并阻止调用线程的时间。

using System.Diagnostics;

private static void NOP(double durationSeconds)
{
    var durationTicks = Math.Round(durationSeconds * Stopwatch.Frequency);
    var sw = Stopwatch.StartNew();

    while (sw.ElapsedTicks < durationTicks)
    {

    }
}

示例用法,

private static void Main()
{
    NOP(5); // Wait 5 seconds.

    Console.WriteLine("Hello World!");

    Console.ReadLine();
}

10
这个问题明确要求如何让程序“休眠”少于1毫秒。这个答案将会使用“阻塞”而非“休眠”。因为问题是关于压力测试的,所以在这种情况下,这个答案可能已经足够好了,我无论如何都会投赞成票。 - nathanchere
好的观点。忘了在我的帖子中提到,现在已经更新了。 - Sam
1
为了酷炫一点,你还可以加上 var sw = Stopwatch.StartNew(); 这一行代码,少写一行 :) - TravisWhidden
这里的一个tick有多长,100纳秒?这在每台机器上都是一致的吗? - Saryk
3
根据这篇帖子,一个tick的持续时间取决于操作系统/硬件。但是,您可以使用StopwatchFrequency属性来确定您的机器上tick的确切长度。 - Sam
显示剩余3条评论

13

为什么?
通常一台计算机只有非常有限的CPU和核心数量-你只能得到一小部分独立执行单元。

另一方面,有许多进程和更多的线程。每个线程都需要一些处理器时间,这由Windows内核进程内部分配。通常,Windows会阻塞所有线程,并给特定线程分配一定量的CPU核心时间,然后将上下文切换到其他线程。

当您调用Thread.Sleep无论多少时间,您都会杀死Windows分配给该线程的整个时间段,因为没有理由简单地等待它,上下文会立即切换。下一次当Windows为您的线程分配一些CPU时,可能需要几毫秒。

使用什么?
或者,您可以旋转您的CPU。旋转并不是一件可怕的事情,而且非常有用。例如,它在System.Collections.Concurrent名称空间中经常与非阻塞集合一起使用,例如:

SpinWait sw = new SpinWait();
sw.SpinOnce();

8
大多数使用Thread.Sleep(1)Thread.Sleep(0)的合法原因涉及相当高级的线程同步技术。正如Reed所说,传统技术无法获得所需的分辨率。我不确定您想要实现什么,但我认为您希望在1毫秒间隔内发生某些操作。如果是这种情况,请查看多媒体计时器。它们可以提供1ms的分辨率。不幸的是,.NET Framework中没有内置API(据我所知)可以利用此Windows功能。但是,您可以使用Interop层直接调用Win32 API。甚至有一些在C#中执行此操作的示例。

6
在旧时代,当需要亚毫秒分辨率时,您会使用Win32的"QueryPerformanceTimer" API。关于这个主题似乎有更多的信息在Code-Project上:http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx。这不会像Reed Copsey所指出的那样允许您以相同的分辨率“Sleep()”。编辑:正如Reed Copsey和Brian Gideon所指出的那样,在.NET中,QueryPerfomanceTimer已被Stopwatch取代。

2
这并不是阻塞操作,它只是提供了一种测量方法。在 C# 中,Stopwatch 类可以有效地实现这一点(通过内部使用 QueryPerformanceCounter)。 - Reed Copsey
不是,但它提供了更高分辨率的时间参考。我不知道Erez在做什么,但我认为他可以用性能计时器(编辑:StopWatch,不知道这个,谢谢)来以不同的方式解决他的问题... - S.C. Madsen
1
+1:是的,这是一个合理的方法。除此之外,我会提到Stopwatch而不是QueryPerformanceCounter - Brian Gideon
谢谢Brian,是的,我完全同意应该使用Stopwatch。我会在我的回复中加上这个建议。 - S.C. Madsen

1
我正在寻找与OP相同的东西,并成功地找到了适合我的答案。我很惊讶其他答案都没有提到这一点。
当你调用Thread.Sleep()时,你可以使用两种重载之一:一个代表毫秒数的int,或者一个TimeSpan。
TimeSpan的构造函数又有多个重载。其中一个是表示TimeSpan所代表的滴答数的单个长整型。一个滴答远小于1毫秒。事实上,TimeSpan文档中的另一部分给出了一个例子,说明在1毫秒内发生了10000个滴答。
因此,我认为最接近问题的答案是,如果你想要Thread.Sleep少于1毫秒,你可以创建一个滴答数少于1毫秒的TimeSpan,然后将其传递给Thread.Sleep()。

我发现在尝试这样做时,任何小于1毫秒的时间实际上并没有等待。示例: Thread.Sleep(Timespan.FromTicks(9999)); = 没有延迟 Thread.Sleep(Timespan.FromTicks(10000)); = 延迟超过1毫秒 - JasonC

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