Какой жизненный цикл имеет задача, созданная через async let, относительно области видимости родителя?
Задача, созданная через async let, не может пережить область видимости родительской задачи. При выходе из этой области Swift неявно дожидается дочерней задачи; если выход вызван ошибкой, дочерняя задача сначала получает запрос на отмену, а затем также ожидается.
Это делает async let частью структурированной конкурентности: дочерняя работа привязана к родителю, её завершение и ошибки не теряются за пределами области видимости.
Асинхронный код без структурированных задач часто создавал проблемы с жизненным циклом: операция могла продолжать работу после уничтожения объекта-владельца, ошибка могла остаться необработанной, а отмену приходилось организовывать вручную.
Подход structured concurrency, частью которого являются async let и группы задач, связывает дочернюю работу с лексической областью родителя. Это упрощает reasoning о завершении, распространении ошибок и отмене.
Представим функцию, которая параллельно загружает два независимых ресурса. Если запустить их как независимые задачи и сразу вернуть результат, одна из операций может продолжить использовать память, сетевое соединение или объект-владелец после завершения функции.
Если родительская операция завершается с ошибкой, важно не только сообщить эту ошибку вызывающему коду, но и корректно завершить дочернюю работу. Иначе появляются утечки логики, гонки при изменении состояния и операции, которые уже не имеют полезного потребителя.
async let создаёт дочернюю задачу, которая наследует структурированную связь с текущей задачей. Она может выполняться конкурентно с родителем, но её результат должен быть получен через await до выхода из области, где объявлена переменная async let.
Если функция выходит обычным return, незавершённые дочерние задачи не остаются работать в фоне: область сначала дожидается их результатов. Если выход происходит из-за ошибки, незавершённым детям отправляется запрос отмены, после чего родитель всё равно ожидает их фактического завершения.
Отмена в Swift кооперативная. Запрос отмены не прерывает произвольный синхронный код и не уничтожает задачу принудительно; дочерняя операция должна проверять отмену или использовать отменяемые API. Поэтому выход из области может задержаться, если дочерняя задача долго выполняет неотменяемую работу.
async let особенно подходит для небольшого фиксированного числа независимых операций. Для динамического числа дочерних задач обычно применяют withTaskGroup или withThrowingTaskGroup. Для работы, которая действительно должна пережить текущую область, используют неструктурированную Task, но тогда её жизненный цикл, отмену и обработку ошибок нужно проектировать вручную.
Экран интернет-магазина одновременно запрашивает карточку товара и рекомендации. Последовательное выполнение проще для чтения, но увеличивает задержку: вторая загрузка начинается только после первой. Две независимые неструктурированные Task уменьшают задержку, однако требуют отдельно хранить ссылки на задачи, отменять их при уходе со страницы и обрабатывать завершение после уничтожения экрана.
Выбран вариант с двумя async let внутри одной асинхронной операции загрузки. Запросы выполняются конкурентно, а выход из операции гарантированно дожидается дочерней работы; при ошибке одной загрузки другая получает отмену и не остаётся бесконтрольно работать в фоне.
Компромисс состоит в том, что функция не завершится мгновенно, если дочерняя операция игнорирует отмену. Это предсказуемее, чем потеря контроля над задачей, но сетевые и вычислительные операции должны быть спроектированы с корректной поддержкой отмены.
async let выполнение дочерних задач параллельно?Нет. Он допускает конкурентное выполнение, но не обещает одновременное выполнение на разных потоках или ядрах. Планировщик Swift может выполнять задачи по очереди, а фактический параллелизм зависит от доступных потоков, блокировок, приоритетов и характера работы.
Главная гарантия async let относится не к параллелизму, а к структуре жизненного цикла: дочерняя задача связана с родительской и не переживает её область видимости.
async let, если родительская функция выбросит ошибку раньше?При выходе по ошибке незавершённая дочерняя задача будет отменена, а затем родительская область дождётся её завершения. Это не означает мгновенного прекращения работы: отмена кооперативная.
Если дочерняя задача успела завершиться с собственной ошибкой, основная ошибка, наблюдаемая вызывающим кодом, определяется тем, как и в какой момент выполняется ожидание. Поэтому нельзя рассчитывать, что все ошибки параллельных операций автоматически будут собраны в единый список.
Task, а не async let?async let предназначен для работы, результат которой является частью текущей операции. Родитель обязан учесть эту дочернюю работу до выхода из области.
Если событие должно отправляться независимо от завершения текущего метода, неструктурированная Task может быть уместнее. Но она не получает автоматического жизненного цикла области: владелец должен хранить её, отменять при необходимости и решать, что делать с ошибками. Такой выбор расширяет свободу, но одновременно возвращает риски, которые структурированная конкурентность устраняет.