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