Заключительной темой нашего обсуждения в этой плаве, посвященной потокам, будет пул потоков CLR. При асинхронном вызове типов с помощью делегатов (посредством метода BeginInvoke) нельзя сказать, что среда CLR буквально создает совершенно новый поток. В целях эффективности метод BeginInvoke делегата использует пул (динамическую область) рабочих потоков, поддерживаемых средой выполнения. Чтобы позволить вам взаимодействовать с этим пулом рабочих потоков, пространство имен System.Threading предлагает тип класса ThreadPool.
Чтобы поставить вызов метода в очередь для обработки рабочим потоком из пула, используйте метод ThreadPool.QueueUserWorkItem. Этот метод является перегруженным, чтобы вдобавок к экземпляру делегата WaitCallback имелась возможность указать необязательный System.Objеct для пользовательских данных состояния.
public sealed class ThreadPool {
…
public static bool QueueUserWorkItem(WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
}
Делегат WaitCallback может указывать на любой метод, имеющий один параметр System.Object (для представления необязательных данных состояния) и не возвращающий ничего. Если при вызове QueueUserWorkItem вы не предложите System.Object, среда CLR автоматически передаст значение null. Для иллюстрации методов очереди при использовании пула потоков CLR давайте рассмотрим следующую программу, в которой снова используется тип Printer. Но на этот раз мы не будем создавать массив типов Thread вручную, а свяжем метод PrintNumbers с членами пула.
class Program {
static void Main(string[] args) {
Console.WriteLine("Старт главного потока. ThreadID = {0}", Thread.CurrentThread.GetHashCode);
Printer p = new Printer;
WaitCallback workItem = new WaitCallback(PrintTheNumbers);
// Очередь из 10 вызовов метода.
for (int i = 0; i ‹ 10; i++) {
ThreadPool.QueueUserWorkItem(workItem, p);
}
Console.WriteLine("Все задачи в очереди");
Console.ReadLine;
}
static void PrintTheNumbers(object state) {
Printer task = (Printer)state;
task.PrintNumbers;
}
}
Здесь вы можете спросить, разве выгодно использовать поддерживаемый средой CLR пул потоков вместо явного создания объектов Thread? Тогда рассмотрите следующие главные преимущества использования пула.
• Пул потоков управляет потоками эффективнее, поскольку минимизируется число потоков, которые приходится создавать, запускать и останавливать.
• При использовании пула потоков вы можете сосредоточиться на своей конкретной задаче, не отвлекаясь на вопросы инфраструктуры потоков приложения.
Однако управление потоками "вручную" может оказаться предпочтительнее, например, в следующих случаях.
• Если требуется создавать приоритетные потоки или устанавливать приоритеты потоков. Потоки, помещенные в пул, всегда являются фоновыми потоками с обычным уровнем приоритета (ThreadPriority.Normal).
• Если требуется создать поток с фиксированным идентификатором, чтобы име-лаcь возможность завершить, приостановить или обнаружить его по имени.
Исходный код. Проект ThreadPoolApp размещён в подкаталоге, соответствующем главе 14.
На этом наш экскурс в многопоточное программирование .NET завершается. Пространство имен System.Threading, без сомнения, определяет множество других типов, кроме тех
Резюме
Эта глава началась с рассмотрения того, как настроить тип делегата .NET на вызов методов в асинхронной форме. Как было показано, методы BeginInvoke и EndInvoke позволяют косвенно управлять фоновыми потоками с минимальными усилиями и практически без проблем. В ходе обсуждения были рассмотрены интерфейс IAsyncResult и тип класса AsyncResult. Эти типы обеспечивают различные способы синхронизации вызовов и получения возвращаемых значений методов.