Класс 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, где рассказываю про свой опыт в программировании.