Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Получение статистических данных о текущем потоке выполнения
Вспомните, что точка входа исполняемой сборки (т.е. операторы верхнего уровня или метод Main()) запускается в первичном потоке выполнения. Чтобы проиллюстрировать базовое применение типа Thread, предположим, что имеется новый проект консольного приложения по имени ThreadStats. Как вам известно, статическое свойство Thread.CurrentThread извлекает объект Thread, который представляет поток, выполняющийся в текущий момент. Получив текущий поток, можно вывести разнообразные статистические сведения о нем:
// Не забудьте импортировать пространство имен System.Threading.
using System;
using System.Threading;
Console.WriteLine("***** Primary Thread stats *****n");
// Получить имя текущего потока.
Thread primaryThread = Thread.CurrentThread;
primaryThread.Name = "ThePrimaryThread";
// Вывести статистические данные о текущем потоке.
Console.WriteLine("ID of current thread: {0}",
primaryThread.ManagedThreadId); // Идентификатор текущего потока
Console.WriteLine("Thread Name: {0}",
primaryThread.Name); // Имя потока
Console.WriteLine("Has thread started?: {0}",
primaryThread.IsAlive); // Запущен ли поток
Console.WriteLine("Priority Level: {0}",
primaryThread.Priority); // Приоритет потока
Console.WriteLine("Thread State: {0}",
primaryThread.ThreadState); // Состояние потока
Console.ReadLine();
Вот как выглядит вывод:
***** Primary Thread stats *****
ID of current thread: 1
Thread Name: ThePrimaryThread
Has thread started?: True
Priority Level: Normal
Thread State: Running
Свойство Name
Обратите внимание, что класс Thread поддерживает свойство по имени Name. Если значение Name не было установлено, тогда будет возвращаться пустая строка. Однако назначение конкретному объекту Thread дружественного имени может значительно упростить отладку. Во время сеанса отладки в Visual Studio можно открыть окно Threads (Потоки), выбрав пункт меню Debug►Windows►Threads (Отладка► Окна►Потоки). На рис. 15.1 легко заметить, что окно Threads позволяет быстро идентифицировать поток, который нужно диагностировать.
Свойство Priority
Далее обратите внимание, что в типе Thread определено свойство по имени Priority. По умолчанию все потоки имеют уровень приоритета Normal. Тем не менее, в любой момент жизненного цикла потока его можно изменить, используя свойство Priority и связанное с ним перечисление System.Threading.ThreadPriority:
public enum ThreadPriority
{
Lowest,
BelowNormal,
Normal, // Стандартное значение.
AboveNormal,
Highest
}
В случае присваивания уровню приоритета потока значения, отличающегося от стандартного(ThreadPriority.Normal), помните об отсутствии прямого контроля над тем, когда планировщик потоков будет переключать потоки между собой. Уровень приоритета потока предоставляет среде .NET Core Runtime лишь подсказку относительно важности действия потока. Таким образом, поток с уровнем приоритета ThreadPriority.Highest не обязательно гарантированно получит наивысший приоритет.
Опять-таки, если планировщик потоков занят решением определенной задачи (например, синхронизацией объекта, переключением потоков либо их перемещением), то уровень приоритета, скорее всего, будет соответствующим образом изменен. Однако при прочих равных условиях среда .NET Core Runtime прочитает эти значения и проинструктирует планировщик потоков о том, как лучше выделять кванты времени. Потоки с идентичными уровнями приоритета должны получать одинаковое количество времени на выполнение своей работы.
В большинстве случаев необходимость в прямом изменении уровня приоритета потока возникает редко (если вообще возникает). Теоретически можно так повысить уровень приоритета набора потоков, что в итоге воспрепятствовать выполнению низкоприоритетных потоков с их запрошенными уровнями (поэтому соблюдайте осторожность).
Ручное создание вторичных потоков
Когда вы хотите программно создать дополнительные потоки для выполнения какой-то единицы работы, то во время применения типов из пространства имен System.Threading следуйте представленному ниже предсказуемому процессу.
1. Создать метод, который будет служить точкой входа для нового потока.
2. Создать новый делегат ParametrizedThreadStart (или ThreadStart), передав его конструктору адрес метода, который был определен на шаге 1.
3. Создать объект Thread, передав конструктору в качестве аргумента делегат ParametrizedThreadStart/Threadstart.
4. Установить начальные характеристики потока (имя, приоритет и т.д.).
5. Вызвать метод Thread.Start(), что приведет к как можно более скорому запуску потока для метода, на который ссылается делегат, созданный на шаге 2.
Согласно шагу 2 для указания на метод, который будет выполняться во вторичном потоке, можно использовать два разных типа делегата. Делегат ThreadStart способен указывать на любой метод, который не принимает аргументов и ничего не возвращает. Такой делегат может быть полезен, когда метод предназначен просто для запуска в фоновом режиме без дальнейшего взаимодействия с ним.
Ограничение ThreadStart связано с невозможностью передавать ему параметры для обработки. Тем не менее, тип делегата ParametrizedThreadStart позволяет передать единственный параметр типа System.Object. Учитывая, что с помощью System.Object представляется все, что угодно, посредством специального класса или структуры можно передавать любое количество параметров. Однако имейте в виду, что делегаты ThreadStart и ParametrizedThreadStart могут указывать только на методы, возвращающие void.
Работа с делегатом ThreadStart
Чтобы проиллюстрировать процесс построения многопоточного приложения (а также его полезность), создайте проект консольного приложения по имени SimpleMultiThreadApp, которое позволит конечному пользователю выбирать, будет приложение выполнять свою работу в единственном первичном потоке или же разделит рабочую нагрузку с применением двух отдельных потоков выполнения.
После импортирования пространства имен System.Threading определите метод для выполнения работы (возможного) вторичного потока. Чтобы сосредоточиться на механизме построения многопоточных программ, этот метод будет просто выводить на консоль последовательность чисел, делая на каждом шаге паузу примерно в 2 секунды. Ниже показано полное определение класса Printer:
using System;
using System.Threading;
namespace SimpleMultiThreadApp
{
public class Printer
{
public void PrintNumbers()
{
// Вывести информацию о потоке.
Console.WriteLine("-> {0} is executing PrintNumbers()",
Thread.CurrentThread.Name);
// Вывести числа.
Console.Write("Your numbers: ");
for(int i = 0; i < 10; i++)
{
Console.Write("{0}, ", i);
Thread.Sleep(2000);
}
Console.WriteLine();
}
}
}
Добавьте в файл Program.cs операторы верхнего уровня, которые предложат пользователю решить, сколько потоков будет использоваться для выполнения работы приложения: один или два. Если пользователь запрашивает один поток, то нужно просто вызвать метод PrintNumbers() в