Пулы потоков

Если в вашем приложении так много потоков, что большая часть их времени тратится на ожидание на WaitHandle, вы можете уменьшить накладные расходы, используя пул потоков. Экономия достигается за счет объединения WaitHandle нескольких потоков.

Для использования пула потоков нужно зарегистрировать WaitHandle и делегат, который должен быть исполнен, когда WaitHandle будет установлен. Это делается вызовом ThreadPool.RegisterWaitForSingleObject, как в следующем примере:

class Test { static ManualResetEvent starter = new ManualResetEvent(false); public static void Main() { ThreadPool.RegisterWaitForSingleObject(starter, Go, "привет", -1, true); Thread.Sleep(5000); Console.WriteLine("Запускается рабочий поток..."); starter.Set(); Console.ReadLine(); } public static void Go(object data, bool timedOut) { Console.WriteLine("Запущено: " + data); // Выполнение задачи... } }

Консольный вывод (после пятисекундной задержки):

Запускается рабочий поток... Запущено: привет

В дополнение к WaitHandle и делегату RegisterWaitForSingleObject принимает объект-“черный ящик”, который будет передан в ваш делегат (как это было для ParameterizedThreadStart), таймаут в миллисекундах (-1, если таймаут не нужен) и флаг, указывающий, является запрос одноразовым или повторяющимся.

Все потоки пула являются фоновыми, они уничтожаются автоматически, когда завершается основной поток(-и) приложения. Однако ожидание выполнения важной работы в потоках пула перед завершением приложения на вызове Join бессмысленно, так как потоки пула после выполнения работы не завершаются, они используются повторно до завершения родительского процесса. Поэтому чтобы узнать, когда исполняемая работа будет закончена, необходимо посигналить, например, специальным WaitHandle.

Вызов Abort для потоков пула – это тоже плохая идея. Потоки пула должны повторно использоваться в течение всей жизни приложения.

Можно использовать пул потоков и без WaitHandle, вызывая метод QueueUserWorkItem и задавая делегат для немедленного вызова. При этом вы не сохраните потоки для повторного использования, но получите выгоду в другом: пул потоков поддерживает максимальное количество потоков (по умолчанию 25), автоматически выстраивая задачи в очередь, когда их количество превышает эту цифру. Это скорее походит на очередь поставщик/потребитель с 25-ю потребителями! В следующем примере 100 заданий ставятся в очередь к пулу потоков, и только 25 одновременно исполняются. Главный поток ждет, пока все задания не будут выполнены, используя Wait и Pulse:

class Test { static object workerLocker = new object(); static int runningWorkers = 100; public static void Main() { for (int i = 0; i < runningWorkers; i++) ThreadPool.QueueUserWorkItem(Go, i); Console.WriteLine("Ожидаем завершения работы потоков..."); lock (workerLocker) while (runningWorkers > 0) Monitor.Wait(workerLocker); Console.WriteLine("Готово!"); Console.ReadLine(); } public static void Go(object instance) { Console.WriteLine("Запущен: " + instance); Thread.Sleep(1000); Console.WriteLine("Завершен: " + instance); lock (workerLocker) { runningWorkers--; Monitor.Pulse(workerLocker); } } }

Консольный вывод:

Ожидаем завершения работы потоков.. Запущен: 0 Завершен: 0 Запущен: 2 Запущен: 1 Запущен: 3 Завершен: 2 Запущен: 5 Завершен: 1 Запущен: 6 Запущен: 4 ... Завершен: 95 Завершен: 96 Завершен: 97 Завершен: 99 Завершен: 98 Готово!

Если необходимо передать в целевой метод более одного объекта, можно или создать объект с нужными свойствами, или использовать анонимный метод. Например, если метод Go требует два целочисленных параметра, можно запустить его следующим образом:

ThreadPool.QueueUserWorkItem(delegate(object notUsed) { Go(23,34); });

Другой путь в пул потоков – через асинхронные делегаты.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: