当我使用 ThreadPool 启动 3..10 个线程时,我有一个场景。每个线程完成其工作并返回到 ThreadPool。当所有后台线程完成时,在主线程中通知哪些可能的选项?
目前我正在使用一种本土方法,为每个创建的线程增加一个变量,并在后台线程即将完成时减少它。这工作得很好,但我很好奇是否有更好的选择。
当我使用 ThreadPool 启动 3..10 个线程时,我有一个场景。每个线程完成其工作并返回到 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 怎么样,并对其设置与线程池一样多的限制。有一个获取信号量的方法,在你启动线程时调用,在线程结束时释放它,如果你已经占用了所有信号量,则引发一个事件。