问题:
我试图从ThreadPool中抛出6个线程来处理各个任务。每个任务的ManualResetEvent都存储在手动重置事件的数组中。线程数对应于ManualResetEvent数组中的索引。
现在发生的事情是,一旦我启动了这6个线程,我就会移出并等待线程完成。等待线程在主线程中完成。
现在有时会发生的事情是,即使经过很长一段时间(我已经看过2天),我的等待逻辑也不会恢复。这是线程等待逻辑的代码示例
foreach (ManualResetEvent whandle in eventList)
{
try
{
whandle.WaitOne();
}
catch (Exception) { }
}
根据.WaitOne的文档。它是同步调用,如果没有从线程收到Set事件,则线程不返回。
有时我的线程工作量较少,甚至可能在我到达Wait逻辑之前返回。是否有可能.WaitOne()将等待Set()事件,即使它是在过去收到的? 这是等待所有线程关闭的正确逻辑吗?
答案 0 :(得分:3)
(注意:我认为你最好的选择是Parallel.Invoke()
- 请参阅本答案的后面部分。)
你正在做什么通常会正常工作,所以问题可能是你的某个线程由于某种原因而阻塞。
你应该能够很容易地调试它 - 你可以附加调试器并打入程序,然后查看调用堆栈以查看哪些线程被阻止。如果你发现了竞争条件,请准备好让人头疼!
要注意的另一件事是无法执行以下操作:
myEvent.Set();
myEvent.Reset();
.Set()
和.Reset()
之间没有任何内容(或非常少)。如果在多个线程等待myEvent
时执行此操作,其中一些线程将错过正在设置的事件! (这种效果在MSDN上没有很好的记录。)
顺便说一下,你不应该忽略异常 - 总是以某种方式记录它们,至少。
(本节不回答问题,但可能会提供一些有用的信息)
我还想提一种等待线程的替代方法。由于您有一组ManualResetEvent,您可以将它们复制到普通数组并将其传递给WaitHandle.WaitAll()
。
您的代码可能看起来像这样:
WaitHandle.WaitAll(eventList.ToArray());
等待所有线程完成的另一种方法是使用CountdownEvent
。当倒计时到零时,它会发出信号;你以线程数开始计数,每个线程在退出时发出信号。有一个例子here。
如果您的线程没有返回值,并且您想要启动它们然后让启动线程等待它们退出,那么我认为Parallel.Invoke()
将是最好的方式。它避免了你自己处理同步。
(否则,正如svick在上面的评论中所说,使用Task
而不是旧的线程类。)
答案 1 :(得分:3)
我没有直接回答这个问题。以下是应该做的事情:
使用Task.Factory.StartNew
启动任务并使用Task.WaitAll(Task[])
等待它们。您不必以这种方式处理事件。异常将很好地传播到“分叉”线程。您不再需要旧的ThreadPool
API。
希望这有帮助。