Когда мы впервые видим метод Task.cancel(), может показаться, что он работает как kill -9 в Unix: сразу останавливает задачу. Но в Swift Concurrency всё устроено иначе.


🔑 Основная идея: отмена кооперативная

Вызов task.cancel() лишь устанавливает флаг отмены у задачи.
Задача сама должна проверять этот флаг и корректно завершаться.

Простейший пример:

let task = Task {    await performWork()
}
Task {    try? await Task.sleep(nanoseconds: 2_000_000_000)    task.cancel()
}

👉 В этом примере задача остановится после второй итерации, потому что мы явно проверяем Task.isCancelled.


🧩 CancellationToken под капотом

Если заглянуть глубже, Task хранит cancellation flag и передаёт его вниз по иерархии:

  • Родительская задача отменяет дочерние.

  • withTaskGroup автоматически отменяет остальные задачи, если одна завершилась с ошибкой.

  • Но сами операции должны реагировать на отмену.

Это похоже на CancellationToken из других языков (например, в .NET).


⚠️ Важный момент: не всё отменяемо

Некоторые операции в Swift Concurrency «уважают» отмену:

  • Task.sleep выбрасывает CancellationError.

  • URLSession.data(for:) прерывает запрос.

А другие — нет:

  • Тяжёлый CPU-bound цикл без проверки Task.isCancelled будет работать до конца.

  • Старые completion-based API не знают про отмену и нужно оборачивать их вручную.


📌 Пример с сетевым запросом

func fetchUsers() async throws -> [User] {    try Task.checkCancellation() // выбросит CancellationError, если задача отменена    let (data, _) = try await URLSession.shared.data(from: URL(string: "https://api.example.com/users")!)    try Task.checkCancellation()    return try JSONDecoder().decode([User].self, from: data)
}

Здесь мы явно проверяем отмену: если задача уже была помечена как отменённая, выбросится CancellationError.

🔍 Подводные камни

  1. Отмена ≠ мгновенное завершение
    Если в функции нет проверок Task.isCancelled или Task.checkCancellation(), она просто доработает до конца.

  2. Отмена не распространяется на синхронные операции
    Если вы запустили тяжёлый цикл на CPU без await — он не прервётся, пока не завершится.

  3. Отмена вложенных задач
    При использовании withTaskGroup или async let отмена одной задачи может автоматически отменить другие, но это поведение нужно учитывать при проектировании.


👉 На этом этапе мы показали, что Task.cancel() — это всего лишь вежливая просьба: «Пожалуйста, остановись». Но в руках разработчика остаётся обязанность написать код так, чтобы он уважал отмену.