Когда мы впервые видим метод 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.
🔍 Подводные камни
Отмена ≠ мгновенное завершение
Если в функции нет проверокTask.isCancelledилиTask.checkCancellation(), она просто доработает до конца.Отмена не распространяется на синхронные операции
Если вы запустили тяжёлый цикл на CPU без await — он не прервётся, пока не завершится.Отмена вложенных задач
При использованииwithTaskGroupилиasync letотмена одной задачи может автоматически отменить другие, но это поведение нужно учитывать при проектировании.
👉 На этом этапе мы показали, что Task.cancel() — это всего лишь вежливая просьба: «Пожалуйста, остановись». Но в руках разработчика остаётся обязанность написать код так, чтобы он уважал отмену.