10

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

目前我正在使用一种本土方法,为每个创建的线程增加一个变量,并在后台线程即将完成时减少它。这工作得很好,但我很好奇是否有更好的选择。

4

7 回答 7

11

减少变量(在线程之间)是有一点风险的,除非用Interlocked.Decrement. 请注意,它必须位于“finally”块中,以避免在异常情况下丢失它(另外你不想终止进程)。

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

于 2008-12-11T09:05:39.290 回答
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 来跟踪线程何时结束,并在池已关闭时处理尚未启动的线程。

于 2011-12-03T07:35:47.620 回答
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 字典,以便稍后确定线程是否已完成。

于 2008-12-11T11:27:19.083 回答
2

目前还没有内置的方法可以做到这一点——我发现这是使用池线程的最大痛苦之一。

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

于 2008-12-11T09:09:51.543 回答
1

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

于 2008-12-11T10:11:04.867 回答
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 作为参数。

于 2008-12-11T10:15:10.383 回答
0

使用 Semaphore 怎么样,并对其设置与线程池一样多的限制。有一个获取信号量的方法,在你启动线程时调用,在线程结束时释放它,如果你已经占用了所有信号量,则引发一个事件。

于 2008-12-12T11:55:28.377 回答