Внутри области группы задач создана неструктурированная задача: становится ли она дочерней задачей этой гру...

Внутри области группы задач создана неструктурированная задача: становится ли она дочерней задачей этой группы?

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

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

Нет. Задача, созданная через Task, не становится дочерней задачей TaskGroup: группа не ждёт её завершения, не отменяет её автоматически и не получает её результат через собственный механизм сбора результатов.

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

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

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

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

Если создать обычную Task внутри withTaskGroup, визуальная вложенность кода может создать ложное впечатление, что задача принадлежит группе. На практике она может продолжить выполнение после выхода из области группы.

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

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

Только задачи, добавленные в группу специальным механизмом группы, являются её дочерними задачами. Группа отслеживает их завершение, собирает их результаты и применяет правила отмены структурированной конкурентности.

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

func loadAll() async -> [Int] { await withTaskGroup(of: Int?.self, returning: [Int].self) { group in group.addTask { 1 } let detachedFromGroup = Task { 2 } var result: [Int] = [] for await value in group { if let value { result.append(value) } } return result } }

В примере группа ожидает только задачу, добавленную через addTask. detachedFromGroup не учитывается при завершении группы; если его результат нужен, дескриптор необходимо явно сохранить и затем ожидать. Если работа должна быть частью структурированного жизненного цикла, её следует добавлять в группу, а не запускать через Task.

Нельзя считать, что удаление дескриптора автоматически отменит задачу. Для неструктурированной задачи отмену и время ожидания завершения нужно проектировать отдельно, например хранить её дескриптор и явно вызывать отмену.

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

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

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

Правильное решение зависит от намерения: обязательную часть загрузки добавляют в группу, а действительно независимую аналитику передают отдельному объекту-владельцу, который явно управляет её дескриптором. В результате жизненный цикл каждой операции соответствует её ответственности.

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

  1. Передаёт ли обычная Task группе свой результат автоматически?

    Нет. Группа знает только о задачах, добавленных в неё как о дочерних. Результат обычной задачи доступен лишь через её дескриптор и должен быть явно запрошен операцией value.

  2. Наследует ли неструктурированная Task контекст родителя?

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

  3. Что произойдёт с обычной Task, если группа завершилась из-за ошибки?

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