【发布时间】:2015-07-21 14:54:44
【问题描述】:
当我使用 ThreadPool 启动 3..10 个线程时,我有一个场景。 每个线程完成其工作并返回到 ThreadPool。 当所有后台线程都完成时,主线程中可以通知哪些选项?
目前我正在使用一种本土方法,为每个创建的线程增加一个变量,并在后台线程即将完成时减少它。 这工作得很好,但我很好奇是否有更好的选择。
【问题讨论】:
标签: c# multithreading threadpool
当我使用 ThreadPool 启动 3..10 个线程时,我有一个场景。 每个线程完成其工作并返回到 ThreadPool。 当所有后台线程都完成时,主线程中可以通知哪些选项?
目前我正在使用一种本土方法,为每个创建的线程增加一个变量,并在后台线程即将完成时减少它。 这工作得很好,但我很好奇是否有更好的选择。
【问题讨论】:
标签: c# multithreading threadpool
除非使用Interlocked.Decrement,否则减少变量(在线程之间)有点冒险,但如果您有最后一个线程(即当它变为零时)引发事件,这种方法应该没问题。请注意,它必须位于“finally”块中,以避免在异常情况下丢失它(另外你不想终止进程)。
在“Parallel Extensions”(或使用 .NET 4.0)中,您可能还会在此处查看 Parallel.ForEach 选项...这可能是另一种将所有事情作为一个块完成的方式。无需手动观看。
【讨论】:
试试这个:https://bitbucket.org/nevdelap/poolguard
using (var poolGuard = new PoolGuard())
{
for (int i = 0; i < ...
{
ThreadPool.QueueUserWorkItem(ChildThread, poolGuard);
}
// Do stuff.
poolGuard.WaitOne();
// Do stuff that required the child threads to have ended.
void ChildThread(object state)
{
var poolGuard = state as PoolGuard;
if (poolGuard.TryEnter())
{
try
{
// Do stuff.
}
finally
{
poolGuard.Exit();
}
}
}
可以以不同的方式使用多个 PoolGuard 来跟踪线程何时结束,并在池已关闭时处理尚未启动的线程。
【讨论】:
如果要等待的线程不超过 64 个,您可以使用 WaitHandle.WaitAll 方法,如下所示:
List<WaitHandle> events = new List<WaitHandle>();
for (int i = 0; i < 64; i++)
{
ManualResetEvent mre = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(
delegate(object o)
{
Thread.Sleep(TimeSpan.FromMinutes(1));
((ManualResetEvent)o).Set();
},mre);
events.Add(mre);
}
WaitHandle.WaitAll(events.ToArray());
执行将等到所有 ManualResetEvents 都设置完毕,或者,您可以使用 WaitAny 方法。
WaitAny 和 WaitAll 方法将阻止执行,但您可以简单地使用列表,或链接到生成的任务的 ManualResetEvents 字典,以便稍后确定线程是否已完成。
【讨论】:
目前还没有内置的方法可以做到这一点 - 我发现这是使用池线程的最大痛苦之一。
正如 Marc 所说,这是在 Parallel Extensions / .NET 4.0 中修复的那种东西。
【讨论】:
你不能给每个线程一个不同的 ManualResetEvent 并在完成后让每个线程设置事件。然后,在主线程中你可以等待所有传入的事件。
【讨论】:
如果您只想知道所有工作何时完成,并且不需要比这更详细的信息(您的情况似乎如此),那么 Marc 的解决方案是最好的。
如果您希望某个线程生成作业,并希望某个其他线程接收通知,您可以使用 WaitHandle。代码要长得多。
int length = 10;
ManualResetEvent[] waits = new ManualResetEvent[length];
for ( int i = 0; i < length; i++ ) {
waits[i] = new ManualResetEvent( false );
ThreadPool.QueueUserWorkItem( (obj) => {
try {
} finally {
waits[i].Set();
}
} );
}
for ( int i = 0; i < length; i++ ) {
if ( !waits[i].WaitOne() )
break;
}
WaitOne 方法,如所写,总是返回 true,但我这样写是为了让您记住一些重载将 Timeout 作为参数。
【讨论】:
如何使用 Semaphore,并对其设置与线程池一样多的限制。有一个获取信号量的方法,当你启动线程时调用它,当你的线程结束时释放它,如果你已经占用了所有的信号量,就会引发一个事件。
【讨论】: