Всегда ли actor выполняет изолированные вызовы в порядке их поступления?
Нет. Actor сериализует выполнение изолированных участков, но не гарантирует FIFO-порядок обработки независимых вызовов. После приостановки на await один вызов может уступить исполнение, а другой вызов — начать или завершиться раньше него.
Если порядок является частью бизнес-логики, его нужно задавать явно: последовательными await в одной задаче, номерами операций или отдельным протоколом упорядочивания.
Actors появились как средство безопасной работы с изменяемым состоянием без ручной координации потоков и блокировок. Исходная проблема состояла не только в одновременном доступе, но и в сложном управлении временем жизни блокировок, взаимными блокировками и ошибками синхронизации.
В Swift actor isolation переносит проверку доступа к состоянию на компилятор, а выполнение изолированных участков организуется через actor executor. Это упрощает безопасность данных, но не превращает actor в очередь с гарантированным порядком сообщений.
Предположим, два независимых вызова actor-метода запущены почти одновременно. Если разработчик неявно предполагает, что первый вызов обязательно завершится первым, результат может зависеть от планирования задач, приоритетов и точек приостановки.
Особенно опасно это для последовательностей вроде обновления состояния, записи событий или обработки версий данных. Формально доступ к состоянию остаётся сериализованным, но бизнес-порядок операций может нарушиться.
Actor исполняет один изолированный фрагмент кода за раз. Пока такой фрагмент выполняется синхронно и не достигает await, другой изолированный фрагмент этого же actor не может вклиниться в него.
await разделяет работу на части. До приостановки выполняется один фрагмент, затем actor может обслужить другой готовый фрагмент, а продолжение исходного вызова будет выполнено позже. Поэтому сериализация означает отсутствие одновременного выполнения изолированных участков, но не означает гарантированную очередь FIFO.
Ожидание результата одного actor-вызова перед запуском следующего в рамках одной задачи задаёт порядок на уровне этой задачи. Однако два независимых вызова из разных задач не получают такого порядка автоматически.
В этом примере нельзя полагаться на вывод A перед B: вызов A может приостановиться, после чего actor продолжит работу с B. Даже сам момент начала независимых задач не следует считать гарантированным порядком поступления.
Если нужен строгий порядок, операции следует передавать actor вместе с последовательными номерами и обрабатывать их по этим номерам, либо последовательно ожидать завершение предыдущей операции. Первый вариант допускает буферизацию и сложнее, зато подходит для независимых производителей; второй проще, но снижает параллелизм и требует единого управляющего контекста.
Сервис синхронизации хранит в actor текущую версию документа. Две задачи одновременно отправляют изменения: версия 11 и версия 12. Из-за сетевого ожидания изменение версии 12 может попасть в actor раньше версии 11.
Вариант «положиться на порядок вызовов» неверен: actor гарантирует безопасный доступ к состоянию, но не знает, какая версия логически старше. Вариант «запретить всю конкурентность» проще, однако создаёт лишнюю задержку и не устраняет необходимость проверять версии.
Выбранное решение — передавать вместе с изменением его номер версии, а внутри actor принимать только ожидаемую версию и временно удерживать более новые изменения. Такой подход явно выражает требование порядка, сохраняет безопасность actor и не зависит от планировщика; его цена — дополнительное состояние для буфера и обработка пропущенных или устаревших версий.
1. Гарантирует ли последовательность двух await-вызовов в одной задаче порядок их завершения?
Да. Если второй вызов создаётся только после завершения первого await, продолжение задачи не перейдёт ко второму вызову раньше первого результата. Это гарантия конкретной цепочки вызовов, а не глобальный FIFO-порядок для всех обращений к actor.
2. Может ли другой вызов actor вклиниться в метод, внутри которого нет await?
Нет, логически такой метод выполняется как один непрерывный изолированный участок. Планировщик может временно приостановить поток операционной системы, но другой actor-изолированный участок не начнёт изменять состояние в середине этого метода. Поэтому короткие атомарные изменения состояния обычно следует выполнять без искусственных точек приостановки между зависимыми шагами.
3. Обеспечивает ли высокий приоритет задачи более раннюю обработку actor-вызова?
Нет. Приоритет влияет на планирование, но не является контрактом порядка выполнения. Он не должен использоваться как механизм нумерации, очередности или согласования бизнес-операций; для этого нужны явные данные и правила обработки.