При выходе из области withTaskGroup все дочерние задачи уже завершены?
Да. withTaskGroup не возвращает управление вызывающему коду, пока все добавленные дочерние задачи не завершатся. Это гарантия структурированной конкурентности: дочерние задачи не переживают область группы и не становятся бесконтрольными фоновыми задачами.
При этом результаты не обязаны быть получены в порядке добавления задач. Если важен порядок, его нужно восстановить отдельно, например по идентификатору элемента.
Структурированная конкурентность появилась как способ связать жизненный цикл дочерних задач с областью видимости родительской операции. Ранее при ручном создании фоновых задач разработчику приходилось самостоятельно отслеживать их завершение, ошибки и отмену.
withTaskGroup решает проблему динамического параллелизма: количество дочерних задач может определяться во время выполнения, но их жизненный цикл всё равно остаётся ограниченным областью группы.
Представим обработку набора изображений. Для каждого изображения создаётся отдельная задача, а функция должна вернуть массив результатов. Если функция завершится раньше дочерних задач, возможны преждевременное освобождение ресурсов, потеря результатов и выполнение работы после закрытия связанного экрана или отмены операции.
При использовании withTaskGroup такой сценарий невозможен для задач, добавленных через addTask: выход из группы подразумевает, что они завершились. Ошибка часто возникает из-за неверного предположения, что добавление задачи похоже на запуск независимой фоновой работы.
Каждый вызов group.addTask добавляет структурированную дочернюю задачу. Родительская задача может получать её результаты через итерацию по группе, но даже отсутствие явного чтения результатов не отменяет ожидание завершения дочерних задач при выходе из withTaskGroup.
Минимальный пример:
Цикл for await получает результаты по мере завершения задач, поэтому порядок элементов в result может отличаться от порядка во входном массиве. После завершения тела группы Swift дожидается всех дочерних задач; только затем возвращается результат функции.
Отмена распространяется от родительской задачи к дочерним. Однако отмена не является принудительным убийством выполнения: дочерняя задача должна кооперативно реагировать на отмену, например проверять Task.isCancelled или вызывать отменяемые операции. Даже после отмены группа сохраняет структурированное ожидание завершения своих дочерних задач.
Это отличается от Task, созданной независимо от группы: такая задача может продолжить работу после возврата функции, если её явно не отменить. Для фиксированного небольшого числа операций удобнее async let, а для динамического числа однотипных дочерних работ — task group.
Сервис загружает миниатюры для всех элементов галереи, причём количество элементов заранее неизвестно. Вариант с массивом независимых Task даёт гибкость, но требует вручную хранить задачи, отменять их при закрытии экрана и дожидаться завершения; ошибка в управлении легко оставляет сетевую работу в фоне.
async let здесь неудобен, поскольку количество дочерних операций динамическое. Поэтому выбирается withTaskGroup: он позволяет создать задачу для каждого элемента, собрать результаты по мере готовности и гарантировать, что функция не завершится раньше всех дочерних операций.
Если пользователь отменяет загрузку, родительская задача отменяет группу, а сетевой слой должен поддерживать кооперативную отмену. Результат — отсутствие задач, переживающих операцию загрузки, предсказуемое освобождение ресурсов и возможность явно решить, как обрабатывать частичные результаты.
Отменяются ли дочерние задачи автоматически при обычном выходе из withTaskGroup?
Нет, обычный выход сам по себе не означает отмену. Группа дожидается завершения дочерних задач, поэтому они продолжают работу до естественного завершения. Автоматическая отмена применяется в соответствующих сценариях, например при отмене родительской задачи или при ошибке в withThrowingTaskGroup.
Гарантирует ли task group порядок результатов?
Нет. Результаты выдаются в порядке завершения дочерних задач, а не в порядке вызовов addTask. Быстрая задача может завершиться раньше ранее добавленной медленной задачи. Для сохранения исходного порядка нужно возвращать вместе с результатом индекс или идентификатор и затем сортировать либо раскладывать результаты по позициям.
Можно ли считать дочернюю задачу завершённой сразу после установки флага отмены?
Нет. Отмена в Swift кооперативная: установка признака отмены лишь сообщает задаче о желательном прекращении работы. Если дочерняя задача игнорирует отмену и продолжает вычисления, группа будет ждать её фактического завершения. Поэтому длительные циклы и собственные асинхронные операции должны регулярно проверять отмену или использовать отменяемые API.