Гарантирует ли синхронное тело MainActor.run отсутствие передачи управления другой задаче до завершения обновления UI?
Да. Пока выполняется синхронное тело MainActor.run, оно не может приостановиться через await, поэтому другая задача не вклинится в середину этого участка через механизм конкурентного выполнения MainActor. Гарантия действует только для самого тела замыкания: задачи, запущенные внутри него, не обязаны завершиться до возврата из MainActor.run.
Глобальные акторы появились в Swift Concurrency, чтобы явно изолировать состояние, доступное только определённому контексту выполнения. Для пользовательского интерфейса таким контекстом стал MainActor, заменивший неявное распространение соглашения «обновляй UI на главном потоке».
MainActor.run решает практическую задачу перехода из фоновой асинхронной работы к короткому синхронному участку, который должен выполняться в изоляции главного актора.
Фоновая задача часто получает данные, а затем изменяет несколько связанных свойств интерфейса. Если эти изменения выполнять разрозненно, между ними может быть приостановка или другой код может увидеть промежуточное состояние.
Неверно также считать, что MainActor.run автоматически ждёт все асинхронные операции, запущенные внутри замыкания. Если замыкание создаёт отдельную задачу, её жизненный цикл не становится частью MainActor.run.
MainActor.run принимает синхронное замыкание, изолированное MainActor. Если текущая задача находится в другом контексте, она ожидает перехода к MainActor; затем синхронное тело выполняется целиком и управление возвращается вызывающей задаче.
Отсутствие await внутри тела важно: синхронный участок не освобождает исполнитель MainActor до своего завершения. Поэтому последовательность связанных изменений выполняется без конкурентного вклинивания другой задачи на этом же акторе.
В примере оба присваивания происходят в одном непрерывном участке MainActor. Однако это не делает операцию глобально атомарной: код вне MainActor не получает доступ к этим свойствам без соблюдения их изоляции.
Синхронность тела не означает, что оно должно быть коротким любой ценой. Долгая вычислительная или блокирующая операция внутри MainActor.run задержит другие работы MainActor, включая обработку интерфейса. Поэтому в блок следует помещать только быстрые изменения состояния и подготовленные результаты.
Если нужна асинхронная последовательность действий, лучше объявить отдельную @MainActor-изолированную async-функцию и явно учитывать точки await. После каждой такой точки возможна реентерабельность: другой код MainActor может выполниться до продолжения функции.
Экран загружает список товаров в фоновой задаче и затем должен одновременно обновить массив и флаг загрузки. Вариант с двумя отдельными переходами на MainActor формально безопасен по данным, но оставляет возможность наблюдать промежуточное состояние. Вариант с долгой обработкой внутри MainActor.run сохраняет последовательность, но блокирует UI.
Выбранное решение — выполнить подготовку и фильтрацию данных до перехода, а в MainActor.run оставить только два быстрых присваивания. Это сохраняет согласованность состояния и минимизирует время занятия MainActor.
Вариант с запуском новой Task внутри MainActor.run хуже для этой задачи: MainActor.run завершится сразу после запуска дочерней работы и не гарантирует, что UI уже обновлён. Такой подход допустим только при осознанном управлении жизненным циклом отдельной задачи.
Вопрос: Может ли другая задача MainActor выполниться между двумя операциями внутри синхронного тела MainActor.run?
Ответ: Нет, если тело действительно синхронное и не вызывает механизм, который сам передаёт выполнение наружу. Внутри нет await, поэтому текущий job продолжает выполняться до выхода из замыкания. Это не запрещает синхронному коду вызвать блокирующую функцию: такая функция просто надолго задержит остальные работы MainActor.
Вопрос: Гарантирует ли MainActor.run, что задача, созданная внутри его тела, завершится до возврата из MainActor.run?
Ответ: Нет. Создание Task только планирует отдельную задачу; MainActor.run ждёт лишь завершения своего синхронного замыкания. Если результат новой задачи важен для вызывающего кода, её нужно явно ожидать в подходящей структуре, а не полагаться на факт запуска.
Вопрос: Выполняется ли тело MainActor.run немедленно, если вызывающая задача уже изолирована MainActor?
Ответ: Нельзя использовать это как контракт синхронного немедленного выполнения. В таком контексте переход обычно не требует фактической смены исполнителя, но вызывающий код должен рассчитывать только на гарантии изоляции и завершения перед возвратом из MainActor.run, а не на конкретный способ планирования.