В async-функции, выполняющей вычислительный цикл без await, как корректно обнаружить отмену задачи?
Отмена в Swift кооперативная: задача не прерывается принудительно. В вычислительном цикле нужно периодически проверять состояние отмены через Task.checkCancellation() — он выбрасывает CancellationError, — либо через Task.isCancelled, если требуется самостоятельно завершить работу без исключения.
Проверка должна выполняться в самой длительной операции или цикле. Одного вызова cancel() недостаточно: он только устанавливает признак отмены у целевой задачи.
Модель кооперативной отмены появилась как безопасная альтернатива принудительному прерыванию потока или произвольной остановке функции. Async-код может владеть ресурсами, менять состояние и находиться между связанными действиями, поэтому внешнее прерывание в любой инструкции могло бы нарушить инварианты.
Вместо этого Swift отделяет запрос отмены от реакции на него. Код сам выбирает безопасную точку остановки и может корректно освободить ресурсы или сохранить частичный результат.
Представим CPU-затратный цикл, который не вызывает await: проверка отмены не произойдёт автоматически, потому что выполнение не приостанавливается и управление не передаётся планировщику. Такая задача продолжит занимать вычислительные ресурсы даже после запроса отмены.
Если вообще не проверять отмену, пользователь может закрыть экран, а связанная с ним задача продолжит обработку. Если проверять её слишком редко, реакция будет запаздывать; если проверять на каждой элементарной операции, появятся лишние накладные расходы.
Task.checkCancellation() проверяет состояние текущей задачи и при отмене выбрасывает CancellationError. Поэтому вызывающая async-функция должна быть throws либо обработать ошибку внутри себя.
Task.isCancelled выполняет небросающую проверку и возвращает булево значение. Она удобна, когда отмена является обычным результатом работы, а не ошибкой, либо когда нужно самостоятельно выйти из цикла и вернуть накопленный результат.
Проверка должна находиться между логически завершёнными единицами работы. Нельзя оставлять общее состояние в промежуточном, неконсистентном виде и рассчитывать, что отмена безопасно прервёт текущую инструкцию.
Многие отменяемые async-операции сами реагируют на отмену, но это не отменяет обязанности учитывать её в собственном CPU-bound коде. Для больших вычислений полезны пакетная обработка, периодические проверки и корректное освобождение ресурсов.
Важно отличать отмену от ошибки: CancellationError — соглашение о прекращении работы, а не механизм принудительного уничтожения задачи. После перехвата ошибки код может вернуть частичный результат, выполнить очистку или повторно пробросить её — в зависимости от контракта функции.
Сервис формирует превью для нескольких тысяч изображений. Пользователь уходит со страницы, и задача отменяется, но обработка одного изображения занимает заметное время.
Вариант без проверок прост, однако отмена практически не работает: цикл завершится самостоятельно. Проверка только после всей обработки также недостаточна, поскольку одна большая операция может надолго задержать реакцию.
Оптимальное решение — обрабатывать изображения небольшими партиями и вызывать Task.checkCancellation() перед каждой следующей единицей работы. Это сохраняет отзывчивость, позволяет корректно прервать вычисление и не требует небезопасного принудительного завершения.
Если частичный результат допустим, вместо выбрасывания ошибки можно проверить Task.isCancelled, выйти из цикла и вернуть уже готовые превью. Выбор между двумя вариантами определяется API: для операции, которая должна завершиться полностью или неуспешно, уместнее checkCancellation(); для потоковой или пакетной обработки — явный возврат частичного результата.
Task.yield() для обнаружения отмены?Нет. Task.yield() может добровольно передать управление другим задачам, но сам по себе не обязан выбрасывать CancellationError и не заменяет явную проверку. Он улучшает планирование и отзывчивость, тогда как Task.checkCancellation() проверяет контракт отмены.
Task.isCancelled == true сигналом немедленного завершения текущей инструкции?Нет. Это только состояние-флаг. После его проверки текущий код всё ещё должен безопасно завершить работу: освободить ресурсы, откатить незавершённое изменение или выйти из цикла. Swift не прерывает выполняющуюся инструкцию автоматически.
CancellationError и продолжить тот же цикл?Задача останется отменённой: обработка исключения не сбрасывает состояние отмены. Если продолжение работы не предусмотрено контрактом, после очистки нужно вернуть управление или повторно выбросить ошибку. Игнорирование ошибки может привести к бесполезной работе после ухода потребителя и нарушить ожидаемую семантику отмены.