Зачем в Swift нужен withDiscardingTaskGroup, если результаты дочерних задач не собираются?
withDiscardingTaskGroup предназначен для структурированного запуска большого числа дочерних задач, когда их результаты не нужны родителю. Он не превращает задачи в независимые: они остаются привязанными к области действия группы, а выход из неё происходит только после завершения дочерних задач.
Главное преимущество — группа не обязана хранить результаты завершившихся задач. Это уменьшает расход памяти при обработке большого потока однотипных операций.
Структурированная конкурентность Swift связывает жизненный цикл дочерних задач с областью действия родительской операции. Обычный TaskGroup хорошо подходит для сбора результатов, но хранение этих результатов избыточно для задач, выполняющих побочные действия: запись в кэш, отправку метрик, предварительную загрузку или обработку файлов.
withDiscardingTaskGroup решает именно эту проблему: сохраняет структурированные гарантии группы, но отбрасывает значения, возвращаемые дочерними задачами.
Запуск отдельных обычных Task может создать неконтролируемые фоновые операции: вызывающий код не обязан дождаться их завершения, а отмену и ошибки приходится координировать вручную. Это особенно опасно при массовой обработке, когда задачи продолжают работать после завершения операции-владельца.
Использование обычной группы только ради запуска побочных действий тоже имеет цену: результаты дочерних задач могут накапливаться до завершения группы. При большом числе операций это приводит к ненужному удержанию памяти.
withDiscardingTaskGroup запускает дочерние задачи в рамках структурированной области. После выхода из замыкания группы Swift дожидается завершения её дочерних задач; поэтому такая группа не является аналогом «запусти и забудь».
Значения, возвращаемые дочерними задачами, не накапливаются для последующего обхода. Это полезно, когда результатом является Void или когда задача сообщает о завершении побочным эффектом, например обновлением изолированного хранилища.
Минимальный пример:
После завершения warmCache все добавленные задачи завершены. Если родительская задача отменена, отмена распространяется на структурированные дочерние задачи, но сама отмена не обязана мгновенно остановить их: операции должны проверять состояние отмены и корректно реагировать на него.
У withDiscardingTaskGroup нет возможности обработать результаты через for await. Если результаты нужны, следует использовать обычный TaskGroup. Если ошибки дочерних операций должны быть представлены родителю, выбирают throwing-вариант группы и явно определяют политику обработки ошибок.
Сервис при открытии экрана прогревает кэш для 10 000 изображений. Возвращаемые данные не нужны: каждая дочерняя задача сохраняет результат в кэш.
Отдельные Task дали бы простую реализацию, но усложнили бы отмену при уходе со страницы и позволили бы операциям пережить владельца. Обычный TaskGroup сохранил бы структурированность, однако был бы избыточен из-за ненужных результатов.
Выбран withDiscardingTaskGroup: задачи остаются ограниченными жизненным циклом прогрева, результаты не накапливаются, а отмена и ожидание завершения подчиняются правилам структурированной конкурентности. В итоге снижается потребление памяти без потери контроля над жизненным циклом операций.
1. Вопрос: гарантирует ли withDiscardingTaskGroup последовательное выполнение дочерних задач?
Нет. Дочерние задачи могут выполняться конкурентно, а конкретный порядок их запуска и завершения не гарантируется. Для последовательной обработки нужно явно организовать её в одной задаче либо использовать синхронизацию и изоляцию состояния.
2. Вопрос: можно ли выйти из withDiscardingTaskGroup, оставив его дочерние задачи работать в фоне?
Нет. Группа структурированная: её область не завершается, пока дочерние задачи не завершены или не обработана соответствующая ошибка. Если требуется независимый жизненный цикл, используется обычная Task, но тогда разработчик сам отвечает за отмену, удержание дескриптора и обработку ошибок.
3. Вопрос: означает ли отбрасывание результата, что задача не может изменить общее состояние?
Нет. Отбрасывается только возвращаемое значение дочерней задачи. Она всё ещё может выполнять побочные действия, но доступ к общему изменяемому состоянию должен быть безопасным: например, через actor, глобальный актор или подходящую синхронизацию. Сам факт использования withDiscardingTaskGroup гонки данных не устраняет.