Синтаксис, который я использую, немного отличается от предложенного @Matt:
find $directory -type f -name \*.in
(это на одно нажатие клавиши меньше).
Вероятно, вы захотите взглянуть на AutoResetEvent и ManualResetEvent.
Они предназначены именно для этой ситуации (ожидание завершения потока ThreadPool перед тем, как что-то сделать).
Вы бы сделали что-то вроде этого:
static void Main(string[] args)
{
List<ManualResetEvent> resetEvents = new List<ManualResetEvent>();
foreach (var x in Enumerable.Range(1, WORKER_COUNT))
{
ManualResetEvent resetEvent = new ManualResetEvent();
ThreadPool.QueueUserWorkItem(DoSomething, resetEvent);
resetEvents.Add(resetEvent);
}
// wait for all ManualResetEvents
WaitHandle.WaitAll(resetEvents.ToArray()); // You probably want to use an array instead of a List, a list was just easier for the example :-)
}
public static void DoSomething(object data)
{
ManualResetEvent resetEvent = data as ManualResetEvent;
// Do something
resetEvent.Set();
}
Edit: Забыл упомянуть, что вы можете дождаться одного потока, любого потока и так далее. Также, в зависимости от вашей ситуации, AutoResetEvent может немного упростить ситуацию, поскольку (как следует из названия) может автоматически сигнализировать о событиях: -)
В .NET 4.0 для этого есть новый класс Barrier .
Кроме того, ваш метод не так уж и плох, и вы можете немного оптимизировать, используя только Pulsing, если значение RunningWorkers после декремента равно 0. Это будет выглядеть так:
this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable
foreach (string someString in arrayStrings)
{
ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
//Thread.Sleep(100);
}
// Waiting execution for all queued threads
Monitor.Wait(this.workerLocker);
// Method DoSomething() definition
public void DoSomething(object data)
{
// Do a slow process...
// ...
lock (this.workerLocker)
{
this.RunningWorkers--;
if (this.RunningWorkers == 0)
Monitor.Pulse(this.workerLocker);
}
}
Вы можете использовать EventWaithandle или AutoResetEvent, но все они являются оболочками Win32 API. Поскольку класс Monitor представляет собой чистый управляемый код, в этой ситуации я бы предпочел Monitor.
Я не уверен, что есть на самом деле, я недавно сделал нечто подобное, чтобы сканировать каждый IP-адрес подсети на предмет принятия определенного порта.
Я могу предложить несколько вещей, которые могут улучшить производительность:
Используйте метод SetMaxThreads для ThreadPool для настройки производительности (т. Е. Балансировки одновременного выполнения большого количества потоков и времени блокировки на таком большом количестве потоков) .
Не спите при настройке всех рабочих элементов, в этом нет реальной необходимости (о чем я сразу понимаю. Но спите внутри метода DoSomething, возможно, только на миллисекунду, чтобы другие потоки могли подключиться к нему в случае необходимости.
Я уверен, что вы могли бы реализовать более индивидуальный метод самостоятельно, но я сомневаюсь, что он был бы более эффективным, чем использование ThreadPool.
PS Я не 100 % ясно о причине использования монитора, поскольку вы все равно блокируете? Обратите внимание, что вопрос задан только потому, что я раньше не использовал класс Monitor, а не потому, что я действительно сомневаюсь в его использовании.
Как насчет Fork
и Join
, которые используют только Monitor
;-p
Forker p = new Forker();
foreach (var obj in collection)
{
var tmp = obj;
p.Fork(delegate { DoSomeWork(tmp); });
}
p.Join();
Полный код показан на этом предыдущий ответ .
Или для очереди производителя / потребителя ограниченного размера (потокобезопасность и т. Д.) здесь .
Мне очень нравится Begin- End- Async Pattern , когда мне нужно ждать завершения задач.
Я бы посоветовал вам заключить BeginEnd в рабочий класс:
public class StringWorker
{
private string m_someString;
private IAsyncResult m_result;
private Action DoSomethingDelegate;
public StringWorker(string someString)
{
DoSomethingDelegate = DoSomething;
}
private void DoSomething()
{
throw new NotImplementedException();
}
public IAsyncResult BeginDoSomething()
{
if (m_result != null) { throw new InvalidOperationException(); }
m_result = DoSomethingDelegate.BeginInvoke(null, null);
return m_result;
}
public void EndDoSomething()
{
DoSomethingDelegate.EndInvoke(m_result);
}
}
Для начала и работы используйте этот фрагмент кода:
List<StringWorker> workers = new List<StringWorker>();
foreach (var someString in arrayStrings)
{
StringWorker worker = new StringWorker(someString);
worker.BeginDoSomething();
workers.Add(worker);
}
foreach (var worker in workers)
{
worker.EndDoSomething();
}
Console.WriteLine("END");
Вот и все.
Примечания: Если вы хотите получить результат из BeginEnd, измените «Action» на Func и измените EndDoSomething, чтобы вернуть тип.
public class StringWorker
{
private string m_someString;
private IAsyncResult m_result;
private Func<string> DoSomethingDelegate;
public StringWorker(string someString)
{
DoSomethingDelegate = DoSomething;
}
private string DoSomething()
{
throw new NotImplementedException();
}
public IAsyncResult BeginDoSomething()
{
if (m_result != null) { throw new InvalidOperationException(); }
m_result = DoSomethingDelegate.BeginInvoke(null, null);
return m_result;
}
public string EndDoSomething()
{
return DoSomethingDelegate.EndInvoke(m_result);
}
}
В дополнение к Barrier, на что указал Хенк Холтерман (кстати, это очень плохое использование Barrier, см. Мой комментарий к его ответу), .NET 4.0 предоставляет все множество других опций (чтобы использовать их в .NET 3.5, вам нужно загрузить дополнительную DLL от Microsoft ). Я написал в блоге сообщение , в котором перечислены все , но мой любимый, безусловно, Parallel.ForEach:
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
За кулисами Parallel.ForEach ставит очередь в новый и улучшенный пул потоков и ждет, пока все потоки не будут выполнены.
Используйте Spring Threading. В него встроены реализации Barrier.
Yes, there is.
1) a counter and a wait handle
int ActiveCount = 1; // 1 (!) is important
EventWaitHandle ewhAllDone = new EventWaitHandle(false, ResetMode.Manual);
2) adding loop
foreach (string someString in arrayStrings)
{
Interlocked.Increment(ref ActiveCount);
ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
// Thread.Sleep(100); // you really need this sleep ?
}
PostActionCheck();
ewhAllDone.Wait();
3) DoSomething
should look like
{
try
{
// some long executing code
}
finally
{
// ....
PostActionCheck();
}
}
4) where PostActionCheck
is
void PostActionCheck()
{
if (Interlocked.Decrement(ref ActiveCount) == 0)
ewhAllDone.Set();
}
ActiveCount is initialized with 1
, and then get incremented n
times.
PostActionCheck
is called n + 1
times. The last one will trigger the event.
The benefit of this solution is that is uses a single kernel object (which is an event), and 2 * n + 1
calls of lightweight API's. (Can there be less ?)
I wrote the code here, I might have misspelled some class names.