Программирование SwiftКонкурентностьРазработчик приложений на Swift

Практическая ситуация: задача Swift получила сигнал отмены — что гарантирует сам факт отмены её немедленной...

Практическая ситуация: задача Swift получила сигнал отмены — что гарантирует сам факт отмены её немедленной остановке?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Сам факт отмены не гарантирует немедленную остановку задачи. В Swift отмена кооперативная: задача должна сама проверить состояние отмены или вызвать операцию, которая реагирует на отмену, после чего завершиться или обработать ошибку.

Исторический контекст

Конкурентные программы нуждаются в безопасном способе прекращать ненужную работу: например, после ухода пользователя со страницы или запуска нового поиска. Принудительное прерывание произвольного кода опасно, потому что оно может оставить ресурсы, инварианты и частично изменённое состояние в неконсистентном виде.

Модель кооперативной отмены переносит ответственность на саму задачу. Это согласуется со структурированной конкурентностью: родительская операция может сообщить дочерним задачам, что результат больше не нужен, а дочерние задачи корректно завершат работу.

Постановка проблемы

Вызов отмены только помечает задачу как отменённую. Если задача выполняет длинный синхронный цикл без проверок или ожидает операцию, не реагирующую на отмену, она может продолжать работу и потреблять процессор, память или сетевые ресурсы.

Неверное предположение о немедленной остановке приводит к лишним запросам, обновлению устаревшего интерфейса, утечке временных ресурсов и неожиданному выполнению побочных эффектов. Поэтому отмену нужно рассматривать как запрос на завершение, а не как принудительное прерывание потока.

Подробное решение

Состояние отмены хранится в задаче и доступно через Task.isCancelled. Проверка этого свойства не завершает задачу автоматически; она только позволяет коду выбрать корректный путь выхода.

Task.checkCancellation() проверяет состояние и выбрасывает CancellationError, если задача отменена. Это удобно для последовательности асинхронных операций: ошибка распространяется вверх, если её не перехватить специально.

func loadAll() async throws -> [Item] { var result: [Item] = [] for page in 1...10 { try Task.checkCancellation() let items = try await loadPage(page) result.append(contentsOf: items) } return result }

Вызов cancel() не обязан прерывать произвольную синхронную работу. Асинхронные API могут реагировать на отмену по-разному: выбрасывать CancellationError, возвращать частичный результат или продолжать операцию, если их контракт этого требует.

Проверку следует размещать в точках, где безопасно отказаться от дальнейшей работы: между итерациями, после дорогостоящего шага и перед публикацией результата. Освобождение ресурсов и другой обязательный cleanup нужно выполнять независимо от того, произошла отмена или обычное завершение.

Отмена также не означает автоматический откат уже выполненных действий. Если задача успела записать данные, отправить запрос или изменить внешний ресурс, отмена не возвращает систему в прежнее состояние; для этого нужна отдельная логика компенсации или транзакционная гарантия.

Ситуация из практики

В приложении поиск запускается при каждом изменении текста. Старый запрос нужно отменять, когда пользователь вводит следующий символ. Рассматривались три варианта: игнорировать старые задачи, принудительно блокировать их результат и использовать кооперативную отмену.

Игнорирование просто, но сохраняет сетевую и вычислительную нагрузку. Блокировка результата защищает интерфейс от устаревших данных, однако сама работа всё равно продолжается. Кооперативная отмена уменьшает лишнюю работу, но требует, чтобы сетевой слой и обработка результатов действительно реагировали на отмену.

Выбран вариант с отменой предыдущей задачи, проверками отмены между этапами обработки и защитой от публикации результата после отмены. Это сочетает экономию ресурсов с корректностью интерфейса; при этом уже отправленный сетевой запрос может завершиться, если используемая библиотека не поддерживает его отмену.

Что кандидаты часто упускают

  1. Передаётся ли отмена автоматически всем задачам, созданным внутри отменённой задачи?

    Для дочерних задач структурированной конкурентности отмена родителя распространяется вниз. Это не означает мгновенное завершение: каждая дочерняя задача должна кооперативно отреагировать на состояние отмены. Задача, созданная как независимая Task.detached, не получает такую структурную связь автоматически и обычно требует отдельного управления её жизненным циклом.

  2. Достаточно ли проверять отмену только перед началом длительной операции?

    Нет. Задача может быть отменена уже после проверки, поэтому одна проверка не создаёт гарантии, что последующая работа будет выполнена без отмены. Проверки нужны в разумных контрольных точках, а сама операция должна корректно обрабатывать отмену; перед публикацией результата часто требуется дополнительная проверка или проверка на уровне владельца состояния.

  3. Нужно ли считать CancellationError обычной ошибкой приложения?

    Обычно отмена означает штатное прекращение работы, а не неисправность. Её можно перехватить для cleanup или изменения состояния интерфейса, но не следует бездумно показывать пользователю сообщение об ошибке и тем более подавлять отмену, продолжая дорогостоящую обработку.

    При этом отмену нельзя всегда просто игнорировать: вызывающий код может ожидать, что отменённая операция не опубликует результат. Если локальная функция перехватывает CancellationError, она должна явно решить, зачем продолжает работу и какие гарантии сохраняет.