получать уведомления, когда все фоновые потоки пула потоков завершены

StackOverflow https://stackoverflow.com/questions/358721

  •  21-08-2019
  •  | 
  •  

Вопрос

У меня есть сценарий, когда я запускаю 3..10 потоков с помощью ThreadPool.Каждый поток выполняет свою работу и возвращается в ThreadPool.Каковы возможные варианты уведомления в основном потоке о завершении всех фоновых потоков?

В настоящее время я использую собственный метод с увеличением переменной для каждого из созданных потоков и уменьшением ее, когда фоновый поток вот-вот завершится.Это прекрасно работает, но мне было любопытно, есть ли варианты получше.

Это было полезно?

Решение

Уменьшение переменной (между потоками) немного рискованно, если это не делается с помощью Interlocked.Decrement, но этот подход подойдет, если у вас есть последний поток (т.когда он достигнет нуля) вызовет событие.Обратите внимание, что он должен находиться в блоке «finally», чтобы не потерять его в случае исключений (плюс вы не хотите завершать процесс).

В разделе «Параллельные расширения» (или в .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();
        }
    }
}

Несколько PoolGuards можно использовать по-разному, чтобы отслеживать завершение потоков и обрабатывать потоки, которые еще не стартовали, когда пул уже закрыт.

Если количество потоков ожидания не превышает 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, связанный с создаваемой задачей, чтобы позже определить, завершился ли поток.

На данный момент не существует встроенного способа сделать это — я считаю, что это одна из самых больших проблем при использовании потоков пула.

Как говорит Марк, подобные проблемы исправляются в Parallel Extensions/.NET 4.0.

Не могли бы вы дать каждому потоку отдельное событие ManualResetEvent и каждый из них устанавливал это событие по завершении?Затем в основном потоке вы можете дождаться всех переданных событий.

Решение Марка лучше всего подходит, если вы просто хотите знать, когда все задания будут завершены, и вам не нужна более подробная информация (как, похоже, в вашем случае).

Если вы хотите, чтобы какой-то поток создавал задания, а какой-то другой поток получал уведомления, вы можете использовать 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.

А как насчет использования семафора и установки его ограничения в пределах вашего пула потоков.Имейте метод для получения семафора, который будет вызываться при запуске потока, освобождать его, когда поток завершается, и вызывать событие, если вы использовали весь семафор.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top