【问题标题】:be notified when all background threadpool threads are finished当所有后台线程池线程完成时收到通知
【发布时间】:2015-07-21 14:54:44
【问题描述】:

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

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

【问题讨论】:

    标签: c# multithreading threadpool


    【解决方案1】:

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

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

    【讨论】:

    • 嗨,马克。我以线程安全的方式进行操作,使用 Interlocked 或 lock 关键字。它可以正常工作并且以非阻塞方式工作。我想知道是否有内置的原语可以做到这一点。谢谢。
    【解决方案2】:

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

    【讨论】:

    • 文章不再可用。什么是 PoolGuard 类?你能复制完整的代码吗?
    【解决方案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 字典,以便稍后确定线程是否已完成。

    【讨论】:

    • 只要 Main() 不是单线程单元 ([STAThread]),此方法就可以很好地工作。
    • 哇,这个问题来了......答案已经超过 4 年了......因为我记得 WaitHandle.WaitAll 有一个限制,如果句柄列表有更多,则会引发异常超过 64 项。也许今天已经过时了。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        如果您只想知道所有工作何时完成,并且不需要比这更详细的信息(您的情况似乎如此),那么 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 作为参数。

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-03-24
            • 1970-01-01
            • 2022-01-19
            • 1970-01-01
            • 2023-04-05
            • 2019-10-24
            • 1970-01-01
            相关资源
            最近更新 更多