ManualResetEvent可能的Race条件

时间:2013-04-20 15:30:17

标签: c# multithreading

问题:

我试图从ThreadPool中抛出6个线程来处理各个任务。每个任务的ManualResetEvent都存储在手动重置事件的数组中。线程数对应于ManualResetEvent数组中的索引。

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

现在有时会发生的事情是,即使经过很长一段时间(我已经看过2天),我的等待逻辑也不会恢复。这是线程等待逻辑的代码示例

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

根据.WaitOne的文档。它是同步调用,如果没有从线程收到Set事件,则线程不返回。

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

2 个答案:

答案 0 :(得分:3)

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

你正在做什么通常会正常工作,所以问题可能是你的某个线程由于某种原因而阻塞。

你应该能够很容易地调试它 - 你可以附加调试器并打入程序,然后查看调用堆栈以查看哪些线程被阻止。如果你发现了竞争条件,请准备好让人头疼!

要注意的另一件事是无法执行以下操作:

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

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

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


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

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

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

WaitHandle.WaitAll(eventList.ToArray());

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

Parallel.Invoke()

如果您的线程没有返回值,并且您想要启动它们然后让启动线程等待它们退出,那么我认为Parallel.Invoke()将是最好的方式。它避免了你自己处理同步。

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

答案 1 :(得分:3)

我没有直接回答这个问题。以下是应该做的事情:

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

希望这有帮助。