在所有后台线程池线程完成时得到通知

时间:2008-12-11 08:58:57

标签: c# multithreading threadpool

我有一个使用ThreadPool启动3..10个线程的场景。 每个线程都完成它的工作并返回ThreadPool。 所有后台线程完成后,主线程中可以通知哪些选项?

目前我正在使用一种自行开发的方法,为每个创建的线程递增一个变量,并在后台线程即将完成时递减它。 这很好用,但如果有更好的选择,我很好奇。

7 个答案:

答案 0 :(得分:11)

除非使用Interlocked.Decrement,否则递减变量(在线程之间)有点冒险,但如果你有最后一个线程(即当它变为零)时,该方法应该没有问题。请注意,它必须位于“finally”块中,以避免在异常情况下丢失它(此外,您不想终止该进程)。

在“Parallel Extensions”(或使用.NET 4.0)中,您可能还会查看此处的Parallel.ForEach选项...这可能是将所有内容作为块完成的另一种方法。无需手动全部观看。

答案 1 :(得分:4)

试试这个: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来跟踪线程何时结束,并处理池已经关闭时尚未启动的线程。

答案 2 :(得分:3)

如果要等待的线程不超过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字典,以便稍后确定线程是否已完成。

答案 3 :(得分:2)

目前还没有一种内置方法可以做到这一点 - 我觉得这是使用池线程的最大麻烦之一。

正如Marc所说,这是在Parallel Extensions / .NET 4.0中修复的东西。

答案 4 :(得分:1)

你不能给每个线程一个不同的ManualResetEvent,并在完成后分别设置事件。然后,在主线程中,您可以等待传入的所有事件。

答案 5 :(得分:0)

如果您只想知道所有工作何时完成,并且不需要更精细的信息(就像您的情况一样),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作为参数。

答案 6 :(得分:0)

如何使用Semaphore,并为其设置一个与线程池一样多的限制。有一个获取信号量的方法,在你开始你的线程时被调用,当你的线程结束时释放它,如果你占用了所有的信号量,就会发出一个事件。