OOP Course
Сьогодні

Підрозділ 16.2

Вкладені завдання та масиви завдань. Task<T>

16.2. Вкладені завдання та масиви завдань. Task<T Розглянемо можливості TPL, що дозволяють будувати складніші структури завдань: вкладені завдання, масиви завдань з колективним очікуванням і типізовані завдання

16.2. Вкладені завдання та масиви завдань. Task

Розглянемо можливості TPL, що дозволяють будувати складніші структури завдань: вкладені завдання, масиви завдань з колективним очікуванням і типізовані завдання, що повертають значення.

Вкладені завдання

Завдання може запускати інше завдання зсередини свого тіла — таке завдання називається вкладеним (nested task). За замовчуванням вкладене завдання є незалежним від батьківського: батьківське завдання може завершитись раніше, ніж вкладене. Це може здивувати — інтуїтивно здається, що завдання повинно чекати на всі запущені ним задачі. Але TPL так не поводиться без явної вказівки.

Поведінка «не чекати вкладеного» є навмисним рішенням дизайну: в більшості сценаріїв завдання запускають допоміжні підзавдання, за якими не потрібно стежити. Але якщо потрібно прив'язати вкладене завдання до батьківського, використовується TaskCreationOptions.AttachedToParent.

AttachedToParent: дочірнє завдання

TaskCreationOptions.AttachedToParent перетворює вкладене завдання на дочірнє (child task). Батьківське завдання не може завершитись, доки не завершаться всі його дочірні завдання. Якщо дочірнє завдання кине виняток, він буде перекинутий через батьківське при виклику Wait().

Вкладені завдання: незалежні vs AttachedToParent

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

Масиви завдань

Коли потрібно запустити кілька завдань і відстежити їх стан, зручно зібрати їх у масив або список. Це дозволяє застосовувати Task.WaitAll і Task.WaitAny до довільної кількості завдань без перерахування кожного вручну.

WaitAll vs WaitAny: коли що обирати

Task.WaitAll — для сценарію «потрібні всі результати, щоб рухатись далі». Наприклад, перш ніж видати загальний висновок, лікар чекає на всі аналізи.

Task.WaitAny — для сценарію «реагуй на перший готовий результат». Наприклад, система моніторингу обробляє показники від першого датчика, що надіслав дані, не чекаючи решти:

Task: завдання з результатом

До цього моменту ми розглядали завдання, що нічого не повертають (Task — аналог void). Але часто завдання обчислює щось і результат потрібен у головному потоці. Для цього існує типізована версія — Task<T>, де T — тип значення, що повертається.

Результат отримується через властивість Result. Звернення до Result блокує поточний потік до завершення завдання — аналогічно Wait(). Тому Result є одночасно і очікуванням, і отриманням результату.

Task з класом результату

У реальних клінічних задачах результат — це, як правило, не просте число, а об'єкт із кількома полями:

Кілька Task з WaitAll

Task.WaitAll коректно працює і з масивами Task<T>. Після WaitAll до всіх результатів можна звертатись через Result без додаткового блокування — результати вже готові:

Важливий момент: звернення до Result після WaitAll не блокує — всі завдання вже завершились. Але якщо завдання завершилось з винятком, Result кине AggregateException при зверненні. Завжди обробляйте можливі винятки через try/catch, якщо завдання може завершитись з помилкою.

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