Привет, Хабр! Это Дмитрий Бахтенков. Добро пожаловать в третью часть цикла статей «Многопоточность. Снизу вверх»! Мы уже разобрали процессор и операционную систему, а сегодня поговорим про использование потоков в .NET с помощью языка программирования C#.
Эта статья — обзор основных возможностей взаимодействия с потоками в .NET.
Класс Thread
Класс Thread — это базовый инструмент для создания и управления потоками в C#. Он позволяет запускать отдельные блоки кода параллельно на разных ядрах, что помогает эффективнее использовать ресурсы процессора и повышает отзывчивость приложений.
Создание потока
В первую очередь нам необходимо создать экземпляр класса Thread и передать в его конструктор какой-либо метод или делегат. Например:
var thread = new Thread(() => { Console.WriteLine($"Hello World from thread {Thread.CurrentThread.ManagedThreadId}!"); }); thread.Start();
Выводом этой программы будет что-то вроде:
Hello World from thread 8!
В этом примере кода мы использовали статическое свойство Thread.CurrentThread для получения информации о потоке. В данном случае — для получения идентификатора созданного потока.
Когда операционная система создает поток, ему выделяется некоторый идентификатор. У основного потока (Main thread), в рамках которого работает метод Program.Main, этот идентификатор равен единице. Дополним наш пример:
static void Main() { Console.WriteLine($"Hello World from thread {Thread.CurrentThread.ManagedThreadId}!"); var thread = new Thread(() => { Console.WriteLine($"Hello World from thread {Thread.CurrentThread.ManagedThreadId}!"); }); thread.Start(); }
Вывод:
Hello World from thread 1! Hello World from thread 10!
Ожидание потока
Часто бывает так, что на каком-то из этапов программы нам необходимо дождаться окончания выполнения потока. Для этого используется метод Thread.Join. А для имитации долгой работы — статический метод Thread.Sleep(), который «усыпляет» поток: останавливает его выполнение на переданное количество миллисекунд.
static void Main() { Thread worker = new Thread(() => { Console.WriteLine("Рабочий поток начал выполнение."); Thread.Sleep(1000); // Имитируем выполнение работы Console.WriteLine("Рабочий поток завершил выполнение."); }); worker.Start(); worker.Join(); Console.WriteLine("Основной поток продолжает выполнение после завершения рабочего потока."); }
Вывод программы:
Рабочий поток начал выполнение. Рабочий поток завершил выполнение.
Основной поток продолжает выполнение после завершения рабочего потока.
Что будет, если убрать вызов метода Join из этой программы? Вот что:
Основной поток продолжает выполнение после завершения рабочего потока.
Рабочий поток начал выполнение.
Рабочий поток завершил выполнение.
Статусная модель потока
В .NET каждый поток имеет набор состояний, которые можно отследить с помощью свойства Thread.ThreadState. Оно возвращает комбинацию флагов, описывающих текущее состояние потока. Важно понимать, что эти состояния могут комбинироваться, а их значение динамически меняется по мере выполнения потока. Рассмотрим основные состояния:
-
Unstarted
Поток находится в этом состоянии до вызова метода Start(). Он еще не начал выполнение и не задействует системные ресурсы для выполнения кода. -
Running
После вызова Start() поток переходит в состояние Running, что означает, что он активно выполняется. Однако это состояние может быстро изменяться, поскольку поток может попасть в ожидание или блокировку. -
WaitSleepJoin
Когда поток вызывает методы ожидания (например, Thread.Sleep(), Thread.Join() или ожидает сигнал от синхронизирующего примитива, он переходит в состояние WaitSleepJoin. Это временное состояние, отражающее блокировку потока. -
Suspended
Поток может быть приостановлен (хотя методы Suspend/Resume являются устаревшими и не рекомендуются к использованию). Состояние Suspended означает, что выполнение потока намеренно приостановлено. -
AbortRequested / Aborted
При вызове Abort() потоку устанавливается флаг AbortRequested, а затем, когда поток действительно прерывается, он переходит в состояние Aborted. Использование Abort() нежелательно, так как оно может оставить приложение в непредсказуемом состоянии. -
Stopped
Когда поток завершает выполнение, он получает состояние Stopped, что означает окончание его жизненного цикла.

Рассмотрим пример отслеживания статуса потока от создания до завершения:
static void Main() { Thread worker = new Thread(() => { Console.WriteLine("Поток начал выполнение."); // Поток переходит в состояние WaitSleepJoin на время сна Thread.Sleep(1500); Console.WriteLine("Поток завершает работу."); }); // До старта поток в состоянии Unstarted Console.WriteLine("Состояние потока до старта: " + worker.ThreadState); worker.Start(); // Периодически выводим текущее состояние потока while (worker.IsAlive) { Console.WriteLine("Текущее состояние потока: " + worker.ThreadState); Thread.Sleep(300); } // После завершения поток получает состояние Stopped Console.WriteLine("Состояние потока после завершения: " + worker.ThreadState); }
Вывод программы:
Состояние потока до старта: Unstarted Текущее состояние потока: Running Поток начал выполнение. Текущее состояние потока: WaitSleepJoin Текущее состояние потока: WaitSleepJoin Текущее состояние потока: WaitSleepJoin Текущее состояние потока: WaitSleepJoin Поток завершает работу. Состояние потока после завершения: Stopped
В качестве эксперимента попробуйте вызвать методы Suspend и Abort во время выполнения потока.
Приоритеты потоков
.NET позволяет управлять приоритетами потоков при их создании. Приоритет отвечает за распределение ресурсов на поток и время взятия этого потока в работу планировщиком ОС. На поток с более высоким приоритетом будет выделено больше ресурсов, он распределится на ядро быстрее.
Приоритеты потоков — это подсказка для планировщика ОС, и они не гарантируют абсолютного распределения ресурсов. Неправильное использование приоритетов может привести к неравномерному распределению процессорного времени.
Для управления приоритетами потоков используется перечисление ThreadPriority, которое содержит следующие значения:
-
HighestПоток с этим приоритетом получает наибольшее количество процессорного времени. Используется для задач, требующих максимально быстрого отклика.
-
AboveNormalПоток получает чуть больше процессорного времени, чем поток с нормальным приоритетом. Полезно для задач, которым нужна небольшая дополнительная производительность.
-
NormalЭто значение используется по умолчанию. Поток получает стандартное количество процессорного времени, сбалансированное с другими потоками.
-
BelowNormalПоток получает немного меньше процессорного времени, чем потоки с нормальным приоритетом. Подходит для фоновых задач, выполнение которых не критично.
-
LowestПоток с самым низким приоритетом. Используется для задач, которые могут выполняться в фоновом режиме и не требуют быстрой реакции.
Пример кода:
static void Main() { Thread lowPriorityThread = new Thread(() => Compute("LowPriority")); Thread highPriorityThread = new Thread(() => Compute("HighPriority")); lowPriorityThread.Priority = ThreadPriority.Lowest; highPriorityThread.Priority = ThreadPriority.Highest; lowPriorityThread.Start(); highPriorityThread.Start(); } static void Compute(string threadName) { long count = 0; for (long i = 0; i < 1_000_000_000; i++) { count++; } Console.WriteLine($"Поток '{threadName}' завершил выполнение."); }
В этом примере создается два потока с разными приоритетами. Скорее всего, поток с высоким приоритетом завершится быстрее, и при запуске программы вывод будет следующий:
Поток 'HighPriority' завершил выполнение. Поток 'LowPriority' завершил выполнение.
Заключение
В этой статье мы разобрали класс Thread в C# и его основные методы. Этот класс позволяет гибко управлять потоками — от времени старта до конкретного приоритета, что позволяет контролировать параллелизацию выполнения задач.
Спасибо за прочтение статьи! Также я веду телеграм-канал Flexible Coding, где рассказываю про свой опыт в программировании.
вАЙТИ — DIY-медиа для ИТ-специалистов. Делитесь личными историями про решение самых разных ИТ-задач и получайте вознаграждение.
Еще статьи по теме #Разработка
Многопоточность. Снизу вверх. ОС
Вторая статья из цикла для разработчиков, которые хотят узнать больше о потоках
Многопоточность. Снизу вверх. Процессор
Как работают потоки в многоядерных процессорах
Основные методики сбора и фиксации требований аналитиком
Пять способов глубже понять задачу и пожелания заказчика
Расширяем свой инструментарий React-приложения
Несколько полезных типовых хуков
Как мы увеличили скорость загрузки сайта и подняли трафик на 54%
Пример из моей практики и пара советов
ссылка на оригинал статьи https://habr.com/ru/articles/896412/
Добавить комментарий