получать уведомления, когда все фоновые потоки - PullRequest
10 голосов
/ 11 декабря 2008

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

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

Ответы [ 7 ]

11 голосов
/ 11 декабря 2008

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

В «Параллельных расширениях» (или с .NET 4.0) вы также можете посмотреть параметры Parallel.ForEach здесь ... это может быть еще один способ сделать все как блок. Без необходимости смотреть их все вручную.

4 голосов
/ 03 декабря 2011

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

3 голосов
/ 11 декабря 2008

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

2 голосов
/ 11 декабря 2008

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

Как говорит Марк, такие вещи исправляются в Parallel Extensions / .NET 4.0.

1 голос
/ 11 декабря 2008

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

0 голосов
/ 12 декабря 2008

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

0 голосов
/ 11 декабря 2008

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

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

...