OOP Course
Сьогодні

Підрозділ 16.3

Продовження завдань. ContinueWith

16.3. Продовження завдань. ContinueWith Завдання продовження continuation task — це завдання, яке автоматично запускається після завершення іншого завдання. Це ключовий механізм побудови послідовних ланцюгів об

16.3. Продовження завдань. ContinueWith

Завдання-продовження (continuation task) — це завдання, яке автоматично запускається після завершення іншого завдання. Це ключовий механізм побудови послідовних ланцюгів обробки (pipelines): результат одного кроку передається наступному, а кожен крок запускається лише тоді, коли попередній завершено. На відміну від Wait(), де поточний потік блокується в очікуванні, ContinueWith не блокує — він лише реєструє «що виконати потім» і повертає управління негайно.

Базовий синтаксис ContinueWith

Метод ContinueWith приймає делегат Action<Task> або Action<Task<T>> — параметр prevTask це посилання на попереднє завдання, що щойно завершилось. Через нього можна отримати Id, Status, Exception і, у разі Task<T>, Result попереднього кроку.

Передача результату між завданнями

Найпрактичніший сценарій ContinueWith — ланцюг обробки, де кожен крок отримує результат попереднього через Task<T>:

Ланцюг ContinueWith — клінічний маршрут пацієнта

Довгий ланцюг — pipeline

Кілька ContinueWith можна об'єднати в лінійний ланцюг, де кожен наступний крок запускається після попереднього. Такий патерн називають pipeline (конвеєр):

Кожен крок отримує посилання на попередній Task (параметр t) і може перевірити його статус. Ланцюг виконується суворо послідовно, але не блокує головний потік між кроками — між кроками головний потік вільний.

TaskContinuationOptions: умовні продовження

За замовчуванням ContinueWith запускається після завершення попереднього завдання незалежно від результату — чи воно успішне, чи скасоване, чи завершилось з помилкою. TaskContinuationOptions дозволяє вказати умову, при якій продовження запуститься:

Опція Коли запускається
OnlyOnRanToCompletion Тільки якщо попереднє завершилось успішно
OnlyOnFaulted Тільки якщо попереднє завершилось з винятком
OnlyOnCanceled Тільки якщо попереднє було скасовано
NotOnRanToCompletion Якщо попереднє НЕ успішне
NotOnFaulted Якщо попереднє НЕ з помилкою
ExecuteSynchronously Виконати синхронно в тому ж потоці

Зверніть: якщо умова TaskContinuationOptions не виконана, продовження переходить у стан Canceled (а не просто ігнорується). Тому WaitAll на набір умовних продовжень може кинути AggregateException з TaskCanceledException всередині — це очікувана поведінка, яку треба обробляти.

Обробка помилок у ланцюгу

Коли у ланцюгу виникає помилка і не перехоплюється всередині завдання, вона «підіймається» вгору через AggregateException. Правильний патерн — розмістити завдання-обробник помилок наприкінці ланцюга з OnlyOnFaulted:

Такий підхід дозволяє будувати надійні ланцюги з розгалуженням на успішний і помилковий шляхи, не вдаючись до вкладених try/catch всередині кожного кроку.

TaskCompletionSource — Task під ручним керуванням

Task.Run() і new Task(...) створюють завдання, що виконують певний делегат у ThreadPool. Але іноді потрібен Task, що завершується не через виконання делегата, а в момент, коли ми самі вирішуємо. Наприклад:

  • Потрібно обернути callback-based API в async-стиль
  • Потрібно «сигналізувати» між частинами системи через Task
  • Потрібно створити Task, що чекає зовнішньої події (таймер, WebSocket-повідомлення, результат від іншого сервісу)

Для цього існує TaskCompletionSource<T> (або TaskCompletionSource без типового параметру для Task):

var tcs = new TaskCompletionSource<string>();

Task<string> task = tcs.Task; // Task, що "ще не завершений"

// Пізніше, в будь-якому місці:
tcs.SetResult("значення");    // Task завершується успішно
tcs.SetException(ex);         // Task завершується з помилкою
tcs.SetCanceled();            // Task скасовується

TaskCompletionSource є «мостом» між callback/event-моделлю і сучасним async/await. Якщо ви отримали API зі старою моделлю (BeginXxx/EndXxx, EventHandler, callbacks) — TaskCompletionSource перетворює її у Task, який можна await-ити.

Важливо: після виклику SetResult, SetException або SetCanceled стан Task зафіксований — повторний виклик кине InvalidOperationException. Для «одноразового сигналу» є зручна альтернатива TrySetResult/TrySetException/TrySetCanceled, що повертають bool замість кидання винятку.

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