【问题标题】:Possible Race condition with ManualResetEventManualResetEvent 可能的竞争条件
【发布时间】:2013-04-13 21:01:08
【问题描述】:

问题:

我正在尝试从 ThreadPool 中抛出 6 个线程来处理单个任务。每个任务的 ManualResetEvent 都存储在手动复位事件数组中。线程数对应 ManualResetEvent Array 中的索引。

现在发生的情况是,一旦我启动了这 6 个线程,我就会离开并等待线程完成。等待线程在主线程中完成。

现在有时发生的情况是,即使经过很长时间(我见过的 2 天),我的等待逻辑也不会返回。这是线程等待逻辑的代码示例

                foreach (ManualResetEvent whandle in eventList)
                {
                    try
                    {
                        whandle.WaitOne();
                    }
                    catch (Exception) { }
                }

根据 .WaitOne 的文档。如果未从线程接收到 Set 事件,则同步调用使线程不返回。

有时我的线程的工作量较少,它们甚至可能在我到达等待逻辑之前返回。 .WaitOne() 是否有可能会等待 Set() 事件,即使它是过去收到的? 这是等待所有线程关闭的正确逻辑吗?

【问题讨论】:

  • 你永远不应该忽略所有异常。如果您确实需要忽略特定异常,请仅忽略它,仅此而已。
  • 另外,你为什么不使用 TPL Tasks?他们让这样的事情变得更简单。

标签: c# multithreading


【解决方案1】:

(注意:我认为您最好的选择是 Parallel.Invoke() - 请参阅此答案的后面部分。)

您所做的工作通常可以正常工作,因此问题可能是您的一个线程由于某种原因而阻塞。

您应该能够很容易地调试它 - 您可以附加调试器并中断程序,然后查看调用堆栈以查看哪些线程被阻塞。但是,如果您发现比赛条件,请做好一些令人头疼的准备!

需要注意的另一件事是您不能执行以下操作:

myEvent.Set();
myEvent.Reset();

.Set().Reset() 之间没有任何(或很少)。如果您在多个线程等待myEvent 时这样做,其中一些将错过正在设置的事件! (这种效果在 MSDN 上没有很好的记录。)

顺便说一句,您不应该忽略异常 - 至少总是以某种方式记录它们。


(本部分不回答问题,但可能会提供一些有用的信息)

我还想提一下另一种等待线程的方法。由于您有一组 ManualResetEvents,您可以将它们复制到一个普通数组并将其传递给WaitHandle.WaitAll()

您的代码可能看起来像这样:

WaitHandle.WaitAll(eventList.ToArray());

另一种等待所有线程完成的方法是使用CountdownEvent。当倒计时达到零时,它会发出信号;您从线程数开始计数,每个线程在退出时都会发出信号。有一个例子here

Parallel.Invoke()

如果您的线程不返回值,而您只想启动它们,然后让启动线程等待它们退出,那么我认为Parallel.Invoke() 将是最好的方法。它避免了您必须自己处理同步。

(否则,正如 svick 在上面的 cmets 中所说,使用 Task 而不是旧线程类。)

【讨论】:

    【解决方案2】:

    我没有直接回答这个问题。这是您应该做的:

    使用Task.Factory.StartNew 启动任务并使用Task.WaitAll(Task[]) 等待它们。您不必以这种方式处理事件。异常会很好地传播到“分叉”线程。您不再需要旧的 ThreadPool API。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      • 2017-07-17
      • 2019-09-09
      • 1970-01-01
      • 2013-05-06
      • 2019-11-25
      • 1970-01-01
      相关资源
      最近更新 更多