JavaRush /Курсы /C# SELF /Приоритеты потоков и типы потоков

Приоритеты потоков и типы потоков

C# SELF
55 уровень , 3 лекция
Открыта

1. Введение

Давайте представим, что потоки — это люди в очереди за мороженым. Кто-то стоит спокойно, а кто-то поднимает шум ("Мне скоро на поезд, пропустите!"), и продавец вроде бы слышит всех, но иногда решает обслужить кого-то вне очереди — например, маму с плачущим ребёнком. Вот приоритеты потоков — это примерно такая же история.

В C# (точнее, в .NET) у каждого потока есть свой приоритет — намёк для операционной системы, насколько этот поток "важен" по сравнению с другими. Это не строгий закон для ОС (никто не гарантирует, что самый приоритетный поток получит всё внимание всегда), но обычно потоки с более высоким приоритетом получают больше процессорного времени.

Где это реально нужно?

  • В пользовательских интерфейсах: например, обновление экрана лучше не тормозить долгими вычислениями с низким приоритетом.
  • В играх: рендеринг кадра — важнее, чем фоновая генерация карты.
  • В задачах с критическим временем реакции: управление оборудованием, обработка сигналов и т.д.

2. Приоритеты потоков: как это устроено

В C# работать с приоритетом потока просто — у объекта Thread есть свойство Priority. Оно принимает значения из перечисления ThreadPriority:

Значение Описание
Lowest
Самый низкий приоритет
BelowNormal
Ниже среднего
Normal
Обычный (по умолчанию)
AboveNormal
Выше среднего
Highest
Самый высокий приоритет

Пример кода:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread lowPriorityThread = new Thread(PrintLowPriority);
        Thread highPriorityThread = new Thread(PrintHighPriority);

        lowPriorityThread.Priority = ThreadPriority.Lowest;
        highPriorityThread.Priority = ThreadPriority.Highest;

        lowPriorityThread.Start();
        highPriorityThread.Start();
    }

    static void PrintLowPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Низкий приоритет: " + i);
            Thread.Sleep(10); // Добавим паузу, чтобы увидеть разницу
        }
    }

    static void PrintHighPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Высокий приоритет: " + i);
            Thread.Sleep(10);
        }
    }
}

На заметку

На практике, особенно на современных многоядерных системах и в управляемой среде .NET, приоритет — это всего лишь рекомендация операционной системе. ОС (и .NET) постараются выделить больше времени потоку с высоким приоритетом, но железных гарантий здесь нет. Например, если ваш поток заберет весь процессор с приоритетом Highest, интерфейс может начать подвисать, а остальные потоки будут страдать.

3. Изменение приоритетов потоков

Потоки и приоритеты

graph LR
    A[Поток 1 - Lowest] -->|Меньше процессорного времени| OS(Операционная система)
    B[Поток 2 - Normal] --> OS
    C[Поток 3 - Highest] -->|Больше процессорного времени| OS
    OS --> CPU(Процессор)

Зачем менять приоритет потока?

Может показаться, что всегда можно ставить Highest, но это плохая идея! Представьте, если все покупатели в магазине начнут одновременно требовать "Обслужите меня первым!".

Изменяйте приоритет только когда это обосновано:

  • Фоновый поток записи логов — можно сделать BelowNormal или Lowest.
  • Поток, обрабатывающий пользовательский ввод — AboveNormal.
  • CPU-емкие задачи, которые не критичны по времени, — низкий приоритет.

Лайфхак: Если приложение начинает тормозить, проверьте, нет ли у вас "наглых" потоков с высоким приоритетом, которые мешают остальным!

4. Типы (категории) потоков в .NET

В C# традиционно выделяют два типа потоков: foreground и background. Удивительно, но "background" — это не тот, который работает "на фоне". Наоборот! Сейчас разберёмся.

Foreground-потоки (основные)

  • По умолчанию все создаваемые вами потоки — foreground.
  • Пока в процессе хоть один foreground-поток жив, приложение не завершится, даже если всё остальное уже "умерло".
  • Пример: основной поток программы (Main), а также все потоки, созданные через new Thread() без дополнительных изменений.

Background-потоки (дочерние)

  • Если все foreground-потоки завершились и остались только поток(и) background — процесс просто завершается, а потоки прерываются без предупреждения.
  • Используются для второстепенных задач: логирование, фоновая отправка метрик и т.п.
  • Чтобы сделать поток фоновым: установите свойство IsBackground в true:
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();

Демонстрация разницы

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread backgroundThread = new Thread(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Background thread работает... " + i);
                Thread.Sleep(500);
            }
            Console.WriteLine("Background поток ЗАВЕРШИЛСЯ.");
        });

        backgroundThread.IsBackground = true; // Делаем поток фоновым!

        backgroundThread.Start();

        Console.WriteLine("Main завершает работу через 1 секунду.");
        Thread.Sleep(1000); // Ждём секунду
        Console.WriteLine("Main завершился. Что будет с background-потоком?");
    }
}

Что увидите?
Main может завершиться, а background-поток "вырубится" на полпути. Это удобно для задач, которые не должны мешать закрытию приложения.

5. Типы потоков

ThreadPool (Пул потоков)

  • ThreadPool — это механизм, который управляет пулом потоков, чтобы не создавать их слишком много.
  • Его используют, когда задач много и они короткие: параллельная обработка запросов, асинхронные операции.
  • Потоки из пула всегда являются background — они не помешают завершению приложения.

Пример: запуск кода в пуле потоков

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "фоновая задача");
        Console.WriteLine("Main завершает работу.");
        Thread.Sleep(500);
    }

    static void DoWorkInThreadPool(object? state)
    {
        Console.WriteLine("Поток из пула: " + state);
        Thread.Sleep(1000); // Попробуем заснуть
        Console.WriteLine("Поток из пула завершился!");
    }
}

На что обратить внимание:
Если бы Main завершился раньше, поток из пула мог бы быть оборван. Если нужна гарантия завершения — используйте foreground-потоки или дожидайтесь явно.

Эволюция многопоточности: задачи, async/await

В современных приложениях редко используют ручное создание потоков (Thread). Обычно применяют Task и асинхронные методы. Сейчас важно знать:

  • Потоки из пула и задачи Task работают на background-потоках.
  • Для большинства задач не нужно менять приоритет — придерживайтесь здравого смысла и лучших практик.

6. Полезные нюансы

Свойства и поведение потоков

Свойство Foreground Thread Background Thread ThreadPool Thread
IsBackground
false по умолчанию true true
Завершение приложения Приложение НЕ завершается, пока жив хотя бы один foreground-поток Приложение завершится, если все foreground-потоки завершились Приложение завершится, как только Main завершится
Управление Полный контроль Полный контроль Нет прямого контроля
Где используется Долгие, важные задачи (например, сервер БД) Некритичные задачи, фоновые операции, логирование Короткие задачи, Task, асинхронные операции
Приоритет Можно задавать Можно задавать Не рекомендуется изменять

Неочевидные фишки и замечания

  • Изменять приоритет можно только для обычных потоков (Thread), а не для задач из пула (Task, ThreadPool).
  • Потоки из пула всегда имеют приоритет Normal (менять нельзя).
  • Task и async/await — более современный подход, при котором вопросы приоритетов и фонового выполнения скрыты "за кулисами".

7. Типичные ошибки с приоритетами и типами потоков

Ошибка №1: злоупотребление приоритетами.
Назначение всем потокам Highest или всем Lowest без объективной причины не приносит пользы. Это может нарушить баланс выполнения задач и снизить отзывчивость приложения.

Ошибка №2: неявное завершение background-потоков.
Если важная работа (например, сохранение данных) выполняется в background-потоке и вы не дожидаетесь её завершения, есть риск потерять данные. Background-потоки автоматически прерываются при завершении процесса.

Ошибка №3: завышенные ожидания от приоритета потока.
Приоритет потока (Priority) — это лишь рекомендация операционной системе, а не гарантия, что поток будет выполнен первым.

2
Задача
C# SELF, 55 уровень, 3 лекция
Недоступна
Установка и проверка приоритета потоков
Установка и проверка приоритета потоков
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ