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

Если текущая задача уже отменена, стартует ли созданная в ней обычная Task отменённой?

Если текущая задача уже отменена, стартует ли созданная в ней обычная Task отменённой?

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

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

Нет. Обычная Task, созданная через Task { ... }, не становится автоматически отменённой только потому, что задача-инициатор уже отменена. Она запускается независимо и будет выполнять работу, пока её явно не отменят или пока сама не завершится.

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

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

Такой механизм удобен для фоновой работы, но он намеренно ослабляет связь с родителем. Ответственность за отмену и время жизни такой задачи переходит к разработчику.

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

Представим, что экран запускает загрузку данных, а затем пользователь закрывает экран. Если загрузка создана как обычная Task, отмена операции закрытия экрана сама по себе не остановит эту загрузку.

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

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

Обычная Task наследует некоторые элементы контекста, например приоритет, task-local значения и изоляцию глобального актора, но не становится структурированным дочерним элементом текущей задачи. Поэтому отмена текущей задачи не распространяется на неё автоматически.

Отмена в Swift кооперативная: даже явно отменённая задача не обязана немедленно прекратить выполнение. Она должна проверить Task.isCancelled или вызвать отменяемую операцию, которая реагирует на отмену, например Task.sleep.

func startWork() async { let worker = Task { while !Task.isCancelled { await processNextItem() } } // Позже отмену нужно передать явно. worker.cancel() _ = await worker.result }

В примере worker.cancel() изменяет состояние именно созданной задачи. Простое наличие отмены у startWork не делает это автоматически.

Если жизненный цикл работы должен быть связан с родителем, предпочтительнее использовать async let или TaskGroup. Их дочерние задачи участвуют в структурированной конкурентности: родитель ожидает их завершения, а отмена и ошибки обрабатываются в рамках соответствующей структуры.

Компромисс обычной Task — независимость в обмен на ручное управление. Она подходит для задач, которые должны пережить текущий вызов, но требует хранить дескриптор, явно вызывать cancel() и продумывать обработку результата.

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

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

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

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

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

1. Наследует ли обычная Task приоритет и task-local значения текущей задачи?

Да, обычно она наследует приоритет, task-local значения и контекст изоляции глобального актора. Это не означает наследование отмены: контекст выполнения и жизненный цикл задачи — разные свойства.

2. Достаточно ли вызвать cancel(), чтобы тело задачи немедленно прекратилось?

Нет. cancel() только устанавливает признак отмены. Тело должно регулярно проверять его либо выполнять операции, которые сами реагируют на отмену; синхронный бесконечный цикл без таких проверок может продолжать работу.

3. Что произойдёт, если сохранить дескриптор Task, но удалить последнюю ссылку на него?

Выполнение задачи от этого не отменяется. Дескриптор представляет доступ к задаче, но его уничтожение не является операцией отмены; отменить работу можно только явно или через механизм, который сам вызывает cancel().