При запуске Task.detached из дочерней задачи какое поведение task-local значения нужно ожидать?
Task.detached не наследует task-local значения родительской задачи. Внутри такой задачи будет видно значение по умолчанию, если оно не задано отдельно; обычная дочерняя Task, напротив, наследует task-local контекст.
Task-local значения появились как безопасная альтернатива глобальному изменяемому состоянию для контекстных данных: идентификатора запроса, локали, настроек трассировки или диагностической информации. Такие данные должны быть доступны вложенным асинхронным операциям, но не должны храниться в общей глобальной переменной.
Модель structured concurrency естественно поддерживает передачу контекста по дереву дочерних задач. Detached task намеренно отделена от этого дерева, поэтому автоматическое наследование контекста для неё отключено.
Представим обработку HTTP-запроса с task-local идентификатором для логирования. Если часть работы запускается через Task.detached, разработчик может ошибочно ожидать, что все записи в журнале будут содержать тот же идентификатор.
В результате detached-задача получит значение по умолчанию. Логи потеряют связь с исходным запросом, а попытка заменить task-local данные глобальной переменной создаст гонки, утечки контекста между запросами и зависимость от порядка выполнения задач.
Task-local значение хранится в контексте текущей задачи и доступно динамически: оно действует внутри области, где было установлено, включая вложенные вызовы и обычные дочерние задачи. При создании Task контекст родительской задачи наследуется, поэтому дочерняя задача видит установленное значение.
Task.detached создаёт независимую задачу без наследования task-local значений. Это согласуется с её назначением: detached-задача не привязана к родительскому дереву задач, не наследует его отмену и не должна неявно зависеть от его контекста.
Task-local значения не являются общей изменяемой памятью. Обычно они задаются на ограниченный участок работы через withValue, а после выхода из этой области автоматически восстанавливается прежний контекст.
Если detached-задаче действительно нужен контекст, его следует передать явно как аргумент. Передаваемое значение должно быть безопасным для передачи между конкурентными задачами; task-local механизм не отменяет требований Sendable и не превращает несинхронизированный ссылочный объект в потокобезопасный.
Сервис логирования устанавливает task-local идентификатор запроса перед обработкой входящего запроса. Основная обработка запускает обычные дочерние задачи, а тяжёлую работу разработчик сначала вынес в Task.detached и обнаружил, что её сообщения не содержат идентификатор запроса.
Вариант с глобальной переменной прост в реализации, но небезопасен: параллельные запросы будут перезаписывать общий идентификатор. Вариант с обычной Task сохраняет контекст, однако не подходит, если работу намеренно требуется отвязать от родительской изоляции или жизненного цикла.
Выбранное решение — явно передать идентификатор detached-задаче параметром и передавать только необходимые данные. Это делает зависимость видимой, сохраняет независимость detached-задачи и исключает неявное ожидание наследования. В результате логи корректно связываются с запросами, а контекст не хранится в глобальном изменяемом состоянии.
Наследует ли обычная Task task-local значения всегда?
Нет, наследование относится к задаче, созданной в текущем контексте через Task, но действует только на момент формирования дочернего контекста. Если значение изменено позже в родительской задаче, уже созданная дочерняя задача не начинает динамически видеть это изменение.
Можно ли изменить task-local значение из дочерней задачи для родителя?
Нет. Task-local значения не являются общей переменной с двусторонней записью. Дочерняя задача получает свой контекст и может временно переопределить значение внутри собственной области, но это не изменяет значение родителя.
Достаточно ли передать task-local значение в detached-задачу, чтобы сделать любую передачу безопасной?
Нет. Явная передача устраняет потерю контекста, но не решает проблему совместного доступа к данным. Если передаётся изменяемый ссылочный объект, он всё ещё может использоваться одновременно из нескольких задач. Нужно передавать значение, соответствующее Sendable, использовать неизменяемые данные или обеспечить синхронизацию через actor либо другой корректный механизм защиты.