2013-04-20 95 views
4

问题:可能的竞争条件与ManualResetEvent

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

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

现在有些时候会发生什么,即使经过很长时间(我已经看到2天),我的等待逻辑也不会返回。这里是线程等待逻辑的代码示例

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

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

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

+4

你不应该忽略所有例外。如果你真的需要忽略一个特定的异常,那就忽略它,没有别的。 – svick 2013-04-20 15:58:30

+0

另外,你为什么不使用TPL'Task's?他们让事情变得更简单。 – svick 2013-04-20 16:00:05

回答

2

(注:我认为最好的办法是Parallel.Invoke() - 见后面的这个答案)

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

你应该能够很容易地进行调试 - 你可以附加调试器并打入程序,然后查看调用堆栈以查看哪个线程被阻塞。如果您发现竞赛状况,请准备好进行一些头部抓挠!

另一件事是知道的,你不能做到以下几点:

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

.Set().Reset()之间没有(或很少)。如果你在多个线程在等待myEvent时这样做,其中一些线程将错过正在设置的事件! (这种效果不是有据可查的MSDN)

顺便说一句,你不应该忽视例外 - 随时登录他们以某种方式,在最起码。


(此部分没有回答这个问题,但它可以提供一些有用的信息)

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

你的代码可能看起来有点像这样:

WaitHandle.WaitAll(eventList.ToArray()); 

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

Parallel.Invoke()

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

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

3

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

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

希望这会有所帮助。