4

问题:

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

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

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

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

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

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

4

2 回答 2

3

我不是直接回答这个问题。这是你应该做的:

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

希望这可以帮助。

于 2013-04-20T16:01:21.190 回答
3

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

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

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

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

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

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

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


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

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

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

WaitHandle.WaitAll(eventList.ToArray());

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

Parallel.Invoke()

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

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

于 2013-04-20T15:46:39.380 回答