OOP Course
Сьогодні

Підрозділ 15.2

Створення потоків. ThreadStart та ParameterizedThreadStart

15.2. Створення потоків. ThreadStart та ParameterizedThreadStart Будь який потік у .NET починається з делегата — посилання на метод, який потік виконуватиме. Конструктор класу Thread приймає делегат однієї з дв

15.2. Створення потоків. ThreadStart та ParameterizedThreadStart

Будь-який потік у .NET починається з делегата — посилання на метод, який потік виконуватиме. Конструктор класу Thread приймає делегат однієї з двох форм: ThreadStart або ParameterizedThreadStart. Розуміння відмінностей між ними та вміння обрати правильний підхід — основа грамотного використання багатопоточності.

Делегат ThreadStart — потік без параметрів

ThreadStart — найпростіша форма: делегат без параметрів і без значення, що повертається:

public delegate void ThreadStart();

Він описує метод, який отримує все необхідне зі свого лексичного оточення (замикання, поля класу, константи) або взагалі не потребує зовнішніх даних. Саме цей делегат підходить для ізольованих завдань, де потік є самодостатнім.

Є три способи передати метод у конструктор Thread: іменований метод, анонімний метод та лямбда-вираз.

Іменований метод

Іменований метод — найзрозуміліший варіант: логіку видно у декларації методу, а не в місці створення потоку. Підходить для складних, об'ємних задач.

Анонімний метод

Анонімний метод зручний, коли логіка проста і тісно пов'язана з контекстом. Він автоматично захоплює змінні зовнішньої функції (замикання).

Лямбда-вираз

Лямбда-вираз є найпоширенішим сучасним підходом: компактна, читабельна, підтримує замикання. Для простих задач — оптимальний вибір.

Делегат ParameterizedThreadStart — потік з параметром

ParameterizedThreadStart відрізняється від ThreadStart одним параметром типу object?:

public delegate void ParameterizedThreadStart(object? obj);

Відповідно, метод Thread.Start(object? parameter) приймає значення, яке буде передане у тіло потоку. Це дозволяє передавати дані у потік у момент його запуску, не вдаючись до замикань чи спільних полів:

Параметр завжди має тип object?, тому в тілі потоку його необхідно привести до потрібного типу. Якщо тип не збігається — as поверне null, що можна перевірити і обробити. Явне приведення через (T) кине InvalidCastException при невідповідності.

Передача кількох значень через клас-обгортку

Найсуттєве обмеження ParameterizedThreadStart — лише один параметр типу object?. Якщо потоку потрібно передати кілька значень, їх пакують у клас або структуру:

Використання is not PatientTask task — це патерн-матчинг: якщо param не є PatientTask, умова спрацьовує і метод завершується. Це безпечніше і лаконічніше за явне приведення та перевірку на null.

Замикання як альтернатива ParameterizedThreadStart

Коли потрібно передати дані в потік, замикання через лямбда-вираз часто зручніше за ParameterizedThreadStart. Лямбда захоплює змінні безпосередньо, без необхідності приведення типів:

Зверніть увагу на рядок string patient = patients[i]: ми копіюємо значення в окрему змінну перед тим, як лямбда її захопить. Це критично важливо. Якщо б лямбда захопила змінну i безпосередньо, всі потоки читали б одне й те саме значення i — те, яке воно буде в момент виконання лямбди, а не в момент її створення. Оскільки цикл завершується швидко, всі потоки могли б прочитати i == 3 і звернутись до одного й того ж елемента масиву (або вийти за межі). Копія виправляє цю проблему.

Порівняння підходів

Усі три підходи — іменований метод, ParameterizedThreadStart і лямбда з замиканням — вирішують одну задачу: зв'язати потік із кодом та даними. Вибір залежить від обсягу логіки та кількості параметрів:

Підхід Коли обирати
Іменований метод + ThreadStart Велика, самодостатня логіка потоку, що варта окремого методу
ParameterizedThreadStart Один або кілька параметрів, що передаються у момент Start()
Лямбда з замиканням Коротка логіка, що використовує змінні навколишнього контексту

Повний клінічний приклад: паралельна обробка черги

Цей приклад демонструє реальну паралельну обробку: чотири лабораторні замовлення виконуються одночасно у чотирьох незалежних потоках. Без багатопоточності загальний час склав би суму всіх затримок (1330 мс); з багатопоточністю — лише час найдовшої операції (450 мс). Метод Join() у фінальному циклі гарантує, що рядок «Всі аналізи оброблено» з'явиться тільки після завершення всіх чотирьох потоків.

Розроблено Tomka Yurii · © 2026 ·