В каком порядке for await выдаёт результаты дочерних задач TaskGroup?
for await выдаёт результаты в порядке завершения дочерних задач, а не в порядке их добавления в TaskGroup или создания исходных входных данных. Поэтому порядок результатов заранее не гарантирован и может меняться между запусками.
В этом примере первым обычно будет не 1, однако Swift не обещает конкретный порядок для остальных результатов.
Task groups появились как часть структурированной конкурентности Swift. Подход решает задачу запуска динамического набора дочерних задач с автоматическим управлением их жизненным циклом вместо ручного хранения дескрипторов и callback-цепочек.
Такой интерфейс естественно ориентирован на обработку события «очередная задача завершилась», поэтому группа предоставляет результаты по мере готовности. Сохранение исходного порядка не является её встроенной семантикой.
Предположим, приложение параллельно загружает данные для нескольких элементов каталога. Если разработчик добавил задачи в порядке [1, 2, 3], это не означает, что результаты будут получены в том же порядке: сетевые задержки, кэш и планирование задач могут различаться.
Если код ошибочно связывает позицию результата с позицией входного элемента, данные могут оказаться сопоставлены неверно. Особенно опасно это при формировании UI-моделей, отчётов или пакетных ответов, где порядок имеет смысл.
TaskGroup реализует асинхронную последовательность. Итерация for await получает следующий уже завершившийся результат; она не извлекает результаты по индексам добавления задач.
Дочерние задачи при этом могут выполняться параллельно. Последовательной является только обработка результатов потребителем: цикл обрабатывает одно полученное значение за раз, но это не превращает выполнение самих дочерних задач в последовательное.
Если порядок важен, его нужно сохранить явно. Обычно дочерняя задача возвращает пару из исходного индекса и результата, после чего родитель сортирует собранные пары или записывает значения в заранее подготовленные позиции.
Если порядок не важен, выдача по мере завершения является преимуществом: приложение может начать обработку быстрых результатов, не дожидаясь самой медленной задачи. Компромисс заключается в том, что обработчик должен быть устойчив к произвольной последовательности.
Сервис одновременно запрашивает профили пользователей и строит итоговый массив в цикле for await. Входные идентификаторы идут в порядке отображения на экране, но ответы приходят в порядке завершения запросов. Прямое добавление результатов в массив нарушает порядок карточек.
Возможны два варианта:
Для экрана выбран второй вариант: сетевые запросы остаются параллельными, а финальная сборка массива выполняется по исходным индексам. Это отделяет порядок выполнения задач от порядка представления данных.
Гарантирует ли порядок вызовов addTask такой же порядок результатов?
Нет. addTask только добавляет дочернюю задачу в группу. Планировщик может запускать задачи с разной задержкой, а внешние операции могут завершаться в произвольной последовательности. Порядок добавления не является контрактом порядка выдачи результатов.
Делает ли for await выполнение дочерних задач последовательным?
Нет. Дочерние задачи запускаются независимо и могут одновременно продвигаться в конкурентном исполнении. for await лишь последовательно получает уже доступные результаты; медленная обработка одного результата может задержать обработку следующих результатов родительским кодом, но не обязана остановить выполнение самих дочерних задач.
Как получить результаты в исходном порядке без последовательного выполнения запросов?
Нужно сохранить идентификатор или индекс вместе с результатом. Родитель собирает пары «индекс — значение», а затем сортирует их по индексу либо записывает значения в соответствующие позиции массива. Так запросы выполняются параллельно, но порядок итоговой коллекции становится детерминированным.