手动复位事件可能出现的竞争状况

本文关键字:竞争 复位 事件 | 更新日期: 2023-09-27 18:10:12

问题:

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

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

现在,有时我的等待逻辑即使在很长一段时间后(我已经看到了2天(也不会返回。以下是线程等待逻辑的代码示例

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

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

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

手动复位事件可能出现的竞争状况

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

使用Task.Factory.StartNew启动任务,然后使用Task.WaitAll(Task[])等待任务。你不必那样处理事件。异常将很好地传播到"分叉"线程。您不再需要旧的ThreadPool API。

希望这能有所帮助。

(注意:我认为你最好的选择是Parallel.Invoke()-见下文的答案。(

你正在做的事情通常会很好地工作,所以问题可能是你的一个线程由于某种原因被阻塞了。

您应该能够很容易地调试它——您可以附加调试器并闯入程序,然后查看调用堆栈以查看哪些线程被阻止。不过,如果你发现比赛状况,请做好抓耳挠腮的准备!

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

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

在CCD_ 5和CCD_。如果在几个线程在myEvent上等待时执行此操作,其中一些线程将错过正在设置的事件!(MSDN上没有很好地记录这种影响。(

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


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

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

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

WaitHandle.WaitAll(eventList.ToArray());

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

Parallel.Invoke()

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

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