Задача отменена во время Task.sleep: какое наблюдаемое поведение должен ожидать вызывающий код?
Task.sleep реагирует на отмену: если задача отменена до завершения ожидания, вызов завершается с ошибкой CancellationError. Это не принудительно уничтожает задачу: ошибка может быть обработана, после чего выполнение продолжится, если код явно не выйдет из функции.
В Swift Concurrency отмена построена как кооперативный механизм. Такой подход позволяет самой операции корректно освободить ресурсы, сохранить инварианты и выбрать подходящее поведение вместо внезапного прерывания произвольной инструкции.
Асинхронные операции вроде Task.sleep специально поддерживают этот механизм, чтобы отменённая задача не удерживала исполнитель неопределённо долго без необходимости.
Представим сетевой или UI-сценарий: задача ожидает таймер, а пользователь покинул экран. Если ожидание не реагирует на отмену, задача продолжит занимать ресурсы и может выполнить устаревшее действие после того, как результат уже не нужен.
Однако обработка CancellationError без выхода из функции также опасна. После перехвата ошибки код может продолжить работу и, например, обновить уже закрытый экран или запустить следующий этап отменённой операции.
Task.sleep проверяет состояние отмены задачи и выбрасывает CancellationError, если отмена обнаружена до окончания сна. Поэтому вызывающий код должен обрабатывать ошибку и обычно завершать текущую операцию после очистки ресурсов.
Отмена устанавливает состояние задачи, но сама по себе не прерывает произвольный синхронный код и не гарантирует немедленное завершение. Если задача выполняет длинный вычислительный цикл без точек приостановки или явных проверок, она должна самостоятельно периодически проверять Task.isCancelled либо вызывать Task.checkCancellation().
Если отмена произошла после фактического завершения сна, Task.sleep может успешно вернуться: результат зависит от того, была ли отмена замечена до завершения операции. После обработки CancellationError задача остаётся отменённой, даже если функция продолжает выполнение; последующие отменочувствительные операции могут снова завершиться ошибкой.
Компромисс заключается в выборе границы отмены. Немедленный выход безопаснее для устаревших операций, но иногда нужно выполнить обязательную очистку или сохранить промежуточное состояние. Для этого сначала освобождают ресурсы, затем прекращают дальнейшую полезную работу.
Экран запускает отложенное обновление данных через несколько секунд. Пользователь закрывает экран до окончания задержки.
Вариант без отменочувствительного ожидания оставит задачу работать до конца. Плюс — простая реализация, минус — лишняя работа и риск применить результат к неактуальному экрану.
Вариант с перехватом CancellationError, но без выхода из функции, корректно прерывает задержку, однако оставляет риск выполнения последующего кода. Это особенно опасно для обновления UI и последовательных сетевых операций.
Предпочтительное решение — сделать ожидание отменяемым, в обработчике выполнить необходимую очистку и завершить операцию. Так задача быстро освобождает ресурсы, а устаревший результат не используется.
Гарантирует ли отмена задачи, что Task.sleep всегда немедленно выбросит ошибку?
Нет. Отмена кооперативна, а результат зависит от момента гонки между завершением сна и обнаружением отмены. Если сон уже завершился, ошибка может не возникнуть. Кроме того, сама отмена не может прервать произвольный синхронный участок.
Что произойдёт, если перехватить CancellationError и продолжить выполнение?
Задача останется помеченной как отменённая, но Swift не остановит её автоматически после catch. Она сможет выполнить последующий код, пока тот сам не проверит отмену или не вызовет другую операцию, реагирующую на неё. Поэтому обычно после очистки используют return либо повторно пробрасывают ошибку.
Чем отличается Task.isCancelled от Task.checkCancellation() в длинном вычислении?
Task.isCancelled только возвращает логическое значение и позволяет выбрать собственную реакцию. Task.checkCancellation() проверяет состояние и выбрасывает CancellationError, что удобно для немедленного выхода из async throws-операции через обычное распространение ошибки. Ни один из этих механизмов не прерывает текущую машинную инструкцию или произвольный синхронный вызов автоматически.