Thread.Sleep或Thread.Yield

37

我有一个使用后台工作者来轮询 DLL 状态的方法,代码大致如下:

var timeout = DateTime.Now.AddSeconds(3);
while (System.Status != Status.Complete  // our status is not complete
       && DateTime.Now < timeout         // have not timed out
       && !_Worker.CancellationPending)  // backgroundworker has not been canceled
{
    //Thread.Yield();
    //Thread.SpinWait(1);
    //Thread.Sleep(1);
}

查看我的CPU使用率时,yield()spinwait()会导致我的应用程序在PC上飙升到50%。使用Sleep(1)时,我的CPU使用率保持在6%以下。有人告诉我应该选择Thread.Yield(),但是CPU使用率的波动令我困扰。对于这种情况,最佳实践是什么?


2
你是否考虑使用 Threading.Timer 来进行轮询处理?如果你正在使用 .NET 4.0,你可以将其与 TPL 混合使用,以实现基于任务的协作取消。 - Bryan Crosby
我猜你的电脑有两个内核。其中一个处于100%繁忙状态... - Eric J.
4
为什么你会使用一个后台工作线程只是为了等待它完成呢?这完全违背常识。而且,如果你需要等待操作完成,为什么 DLL 没有提供一个明智的方法来实现呢? - David Schwartz
1
+1 @DavidSchwartz 建议使用高效的信号通知而不是浪费 CPU 资源和延迟问题的轮询(还有许多其他方法,如 AutoResetEvent、Semaphore 等等)。 - Martin James
2
@poco 简而言之,你被告知错误的信息 - user719662
2个回答

38

Thread.Yield会打断当前线程以允许其他线程进行工作。 但是,如果它们没有要做的工作,您的线程很快将被重新调度并继续轮询,从而占用1个核心的100%利用率。

使调用线程放弃执行权以便让当前处理器上准备运行的另一个线程获得执行权。操作系统选择要放弃执行权的线程。

Thread.Sleep将在睡眠时间过期后再次安排您的线程运行,因此CPU利用率更低。

阻止当前线程,以便指定的毫秒数。

在这两者之间选择,Thread.Sleep更适合您的任务。 但是,我同意@Bryan的评论,使用Threading.Timer可以得到更优雅的解决方案。


2
涉及轮询的解决方案都不太“优雅”。只有在绝对无法通过其他方式与 DLL 通信时才应该使用此类解决方案,例如它是第三方、不透明和设计糟糕的(在这种情况下,设计师应该反复用操作系统手册敲他的头:)。 - Martin James
1
当问题说明必须轮询外部DLL时,我就已经假定了这个限制。 - Eric J.
2
Thread.Yield实际上告诉操作系统将优先级从您的进程中取走(而不是Thread.Sleep,它让操作系统自己解决)。当我使用Thread.Yield时,我会将其与Thread.Sleep结合使用--因此代码基本上是Thread.Yield(); Thread.Sleep(1);--请注意,Thread.Sleep几乎总是会睡眠至少10毫秒(我没有看到一种方法可以在不使用非托管代码或第三方库的情况下获得更好的10毫秒时间分辨率)。 - BrainSlugs83
1
你有这个10毫秒最小延迟的来源吗?我们在将interval设置为零时运行Thread.Sleep(interval)出现了问题。 - Rafael Diego Nicoletti
@RafaelDiegoNicoletti https://dev59.com/AWIk5IYBdhLWcg3wrv24 - JaredBroad
显示剩余4条评论

4
从帖子信息回答这个问题:
“这种情况下最好的做法是什么?”
你似乎有一个SpinWait结构的案例,它存在于.NET 4.0以后,与早期的SpinWait方法不同。
你的代码可以升级为类似于以下内容:
var timeout = DateTime.Now.AddSeconds(3);
var spinWait = new SpinWait();
while (System.Status != Status.Complete
       && DateTime.Now < timeout
       && !_Worker.CancellationPending)
{
    spinWait.SpinOnce();
}

"SpinOnce 方法将决定是否调用 Thread.SleepThread.Yield 或进行“忙等待”,即不进行让步。

关于帖子标题(即 Thread.Sleep vs Thread.Yield),以下是一些最新的答案:

"
  • Thread.Yield(): 如果有另一个线程准备好执行,那么它会将当前 CPU 核心让给该线程。否则什么也不做,即立即返回,这可能导致高 CPU(和高电池消耗),如果系统至少有一个空闲的 CPU 核心。在大多数情况下,在同一 CPU 核心上恢复执行,但由于各种原因(线程可能被 GC 暂停等),这并不能保证。在服务器空间中,它是实现简单忙等待的好方法,但在某些情况下可能比上面解释的 SpinWait 更慢。在 Windows 系统上,它(截至本文写作)实现为调用 Win32 API SwitchToThread
  • Thread.Sleep(0): 将当前线程放在该优先级的操作系统准备执行队列的末尾,如果队列不为空。否则什么也不做,即立即返回。行为与 Thread.Yield() 几乎相同,只是它允许切换 CPU 核心,这往往会在恢复之前给具有相同或较低优先级的线程更多的时间。除了这个额外的时间之外,恢复到不同的 CPU 核心的几率要大得多,导致像 L1 或 L2 缓存之类的东西丢失,并进一步降低性能。只有在饥饿的可能性比使用 Thread.Yield() 更高时才应该使用它。在 Windows 系统上,它(截至本文写作)实现为调用 Win32 API SleepEx
  • Thread.Sleep(1): 如果剩余时间片至少有 1 毫秒,则将 CPU 让给剩余时间片。否则,将 CPU 让给剩余时间片和整个下一个时间片。因为在许多系统中,时间片大约为 15 毫秒,所以平均睡眠时间为约 7 毫秒或 8 毫秒。因此,与 Thread.Yield()Thread.Sleep(0) 不同,Thread.Sleep(1) 保证会花费一些时间让 CPU,这可能会冷却事物。但是,性能成本巨大,因此它应该仅与其他“旋转”或“产生”解决方案一起使用。
关于此事的额外评论:
“然而,CPU%中的尖峰让我感到不安。”
根据您的帖子,这个担忧可能是因为代码有3秒的超时限制。虽然 SpinWait 应该可以解决这个问题,但如果由于某种原因无法使用 SpinWait 结构体,则考虑使用更小的超时时间,例如几毫秒。

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