45
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Многопоточность. Снизу вверх. Потоки в языке C#

Время чтения 13 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
45
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Всем привет, это Дмитрий Бахтенков. Добро пожаловать в третью часть цикла статей «Многопоточность. Снизу вверх»! Мы уже разобрали процессор и операционную систему, а сегодня поговорим про использование потоков в .NET с помощью языка программирования C#.

Эта статья — обзор основных возможностей взаимодействия с потоками в .NET.

Многопоточность. Снизу вверх. Потоки в языке C#

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

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники