在手动重置事件和线程休眠之间做出选择

9

我不确定采用哪种策略...我正在专注于完成我的操作,但我也希望将性能问题降至最低...有一种叫做Execute()的方法必须等待(同步运行),直到操作完成。 这个操作发生在另一个线程上。 有两种实现相同操作的方法...

一种是使用ManualResetEvent

void Execute()
{
    taskHandle = new ManualResetEvent(false);
    .
    .
    //delegate task to another thread
    .
    .
    taskHandle.WaitOne();
}

OR

通过使用简单的while结构

void Execute()
{
    .
    .
    //delegate task to another thread
    .
    .
    while (!JobCompleted)
        Thread.Sleep(1000);
}

我应该采用这两种方法中的哪一种呢?为什么?

编辑:

Q2. 如果我只有一个空的while循环,那有什么区别吗?

while(!JobCompleted);

编辑:(之前我收集的一些信息)

http://www.yoda.arachsys.com/csharp/threads/waithandles.shtml - 这篇文章说手动重置事件相对较慢,因为它们会跳出托管代码再次进入...


只是想指出Thread.Sleep(0)的用法。指定零(0)以表示应暂停此线程以允许其他等待线程执行。在这种情况下,Sleep(0)比Sleep(1000)更好,但使用WaitHandle仍然更合适。 - THX-1138
4
空的 while 循环基本上是一个自旋等待 - 它会完全占用一个 CPU。不建议使用。 - nitzmahone
7个回答

16

出于好奇,为什么要选择ManualResetEvent而不是AutoResetEvent?无论哪种方式,都应该使用操作系统原语而不是一个休眠-检查-休眠的方法。

你也可以使用Monitor锁定(通过Monitor.EnterMonitor.Exit显式地实现,或通过lock块实现),但方法应基于你实际所做的事情;如果情况是“这些东西只有一个,我需要独占访问权限”,那么使用Monitor锁定。如果是“我需要等待直到另一个线程完成,但原因不是资源访问”,则使用AutoResetEventManualResetEvent

使用Thread.Join的建议是很好的,只有当

  1. 你有其他Thread对象的访问权限
  2. 你不想在其他线程终止之前执行。

如果两者都不成立(你无法访问,或其他线程不会终止,它只会发出“全部清除”信号),那么Thread.Join不可行。

最糟糕的选择是

while(!JobCompleted);

因为这会在检查变量时无需任何暂停就占用处理器。是的,它将阻塞你的线程直到操作完成,但你将使用大量CPU(或至少一个核心的价值)。


读到某个地方说手动重置在内核模式下工作...如果是这样,那么应该会有性能开销,对吗?PS:我已经编辑了问题... - deostroll
你建议采用操作系统原语,但你真的应该解释一下为什么。 - Gigi

4
该事件可以更有效地利用处理器,因为你不必唤醒父线程来轮询。内核将在事件触发时唤醒你。

但是与其他构造相比,手动重置事件不是很昂贵吗?我使用的是.NET 2.0。在某个地方读到手动重置工作在内核模式下,而其监视器替代品则在用户模式下工作...因此由于模式的更改而产生额外的开销...!这是真的吗? - deostroll
它们的制作成本比监视器更高,但它们也具有更多功能。这取决于您的创建频率。如果您希望每秒运行数千个,采用轻量级方法会更好。 - nitzmahone

2
如果您可以访问原始的Thread对象,或者可以获得该访问权限,则最好使用Thread.Join()
编辑:此外,如果这是在WinForms或WPF等GUI中进行的,则可能要考虑使用BackgroundWorker

然后像@nitzmahone建议的那样使用ManualResetEvent。或者找到一种获取该线程的方法。使用ManualResetEvent的唯一缺点是调用方和工作者都必须使用它,从而创建可能不需要的依赖关系。 - Randolpho
@Randolpho。难道这两种方法不都必须共享资源才能相互通信吗?在 while 循环的情况下,两个线程都必须知道“JobCompleted”布尔值。 - rocka

2
使用Thread.Sleep()的主要缺点是,你需要决定线程等待的时间。你等待的操作可能需要更多更少的时间,通常很难精确地量化这个时间。如果线程休眠时间过长,那么你就没有充分利用系统资源。
为了达到最佳状态,你应该使用ManualResetEvent(或AutoResetEvent),这样当依赖操作完成时,你的线程就会立即恢复。

1

ManualResetEvent 绝对是最好的选择。

从您提供的代码片段来看,似乎您正在将执行委托给 Execute 方法。如果是这种情况,并且您只委托了一个任务,那么如果您必须等待响应,为什么还要委托到另一个线程呢?您可以同步执行该进程。


1

手动重置事件比较慢,因为它们会从托管代码中退出,然后再进入。

它们可能比使用 Wait/Pulse 组合慢,在我看来,你应该在这里使用它。但是,Manual/AutoResetEvents将比您执行的任何Thread.Sleep(x)快得多,即使您选择x = 1。即使您将Windows计时器分辨率降低到1毫秒。

如果我只有一个空while循环呢?有什么区别吗...?

那么一个核心将以100%的速度旋转,直到条件变为真,从而从其他线程窃取时间,这些线程可能会用它来做一些有用的事情,例如计算“愤怒的小鸟”的帧 - 或者CPU可能会稍微冷却一下,延迟全球变暖的灾难性影响几个纳秒。


0

这两种方法基本上都是做同样的事情。然而,while循环略微更加明确,因为您可以指定睡眠时间。虽然我会使用XXXResetEvent类,这些类旨在用于您正在工作的场景中。我会假设线程类现在或以后会实现更强大的线程代码,以处理多核处理器上的线程亲和性等问题。


ManualResetEvent的行为与定期轮询非常不同。 - Rasmus Faber

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