Согласуется ли одновременный вызов next() у одного AsyncIterator из нескольких задач с контрактом Swift?
Нет, Swift не даёт общей гарантии безопасности или корректного порядка при одновременном вызове next() у одного AsyncIterator. Такой доступ допустим только если конкретная реализация итератора явно документирует поддержку конкурентного потребления.
Обычно один итератор должен принадлежать одной задаче-потребителю. Sendable самого итератора, если он вообще реализован, не означает, что его методы автоматически сериализованы.
AsyncSequence и AsyncIteratorProtocol отделяют источник асинхронных значений от кода, который их последовательно получает. Такой подход решает проблему ручного управления callback-ами, буферами и состояниями ожидания.
При этом протокол описывает способ получения следующего элемента, но не превращает произвольный итератор в многопоточный потокобезопасный объект. Ответственность за модель потребления остаётся у реализации последовательности и её пользователя.
next() часто изменяет внутреннее состояние итератора: позицию чтения, состояние ожидания, признак завершения или ссылку на текущую операцию. Если две задачи вызывают его одновременно, они могут конкурировать за это состояние, получить элементы в неожиданном порядке или нарушить внутренние инварианты.
Для локального изменяемого итератора строгая проверка конкурентности Swift может отклонить передачу или одновременное изменение значения. Но если итератор использует ссылочный тип, небезопасную аннотацию или внешний синхронный механизм, компилятор не обязательно обнаружит логическую гонку.
Контракт AsyncIteratorProtocol не обещает, что несколько вызовов next() одного экземпляра будут автоматически сериализованы. async означает возможность приостановки, но не предоставляет блокировку; во время await другой вызов может начать работу с тем же состоянием.
Кроме того, next() обычно является изменяющим методом итератора. Поэтому одновременный доступ к переменной-итератору может быть запрещён системой типов ещё до выполнения программы. Для ссылочного итератора проблема может проявиться уже во время работы, если его состояние изменяется без синхронизации.
Безопасный базовый вариант — выделить одного владельца итератора и передавать результаты другим задачам через безопасный канал:
Если нужны несколько потребителей, следует проверить документацию конкретной последовательности. Возможные варианты — создать отдельный итератор для каждого независимого потребителя, централизованно читать один итератор и распределять элементы, либо реализовать явную сериализацию доступа. Actor может защищать состояние-координатор, но сам по себе не гарантирует последовательность операций, если его метод освобождает изоляцию на await и допускает повторный вход.
Важно различать безопасность производителя и потребителя. Например, внутренняя реализация конкретного AsyncStream может безопасно принимать значения из нескольких задач, но это не создаёт общего правила для конкурентного вызова next() у любого итератора.
Сервис получает события из одного сетевого источника и передаёт один итератор двум задачам: одна обновляет кэш, другая пишет аудит. Прямое конкурентное чтение кажется простым, но оно связывает обе задачи с неопределённой моделью распределения элементов: событие может достаться только одной задаче, порядок обработки станет неочевидным, а неподходящая реализация итератора может повредить состояние.
Вариант с двумя независимыми итераторами подходит только для последовательностей, которые действительно поддерживают независимые проходы; для единого сетевого источника он часто не даёт нужной семантики. Вариант с блокировкой защищает доступ, но превращает потребление в последовательное и усложняет обработку приостановок.
Предпочтительное решение — одна задача владеет итератором, последовательно получает события и отправляет их в отдельные безопасные очереди или потоки для кэша и аудита. Это явно задаёт порядок чтения, исключает гонку внутри итератора и позволяет независимо отменять downstream-потребителей; цена решения — дополнительная маршрутизация и необходимость определить политику переполнения.
Sendable итератора, что его next() можно вызывать параллельно?Нет. Sendable описывает допустимость передачи значения между конкурентными контекстами и отсутствие запрещённого совместного доступа в соответствии с моделью Swift. Он не является обещанием последовательного выполнения методов и не заменяет mutex, actor или однопоточного владельца.
Не всегда. Если изолированный метод actor вызывает await при получении элемента, actor может приостановить текущий вызов и принять другой. Два вызова могут оказаться одновременно ожидающими один источник, поэтому для строгой очереди нужен дополнительный протокол состояния или единственный внутренний потребитель.
Только если конкретная AsyncSequence определяет независимую итерацию. У последовательности может быть общее внешнее состояние, одноразовый источник или семантика широковещательной доставки; в таком случае отдельные итераторы не гарантируют ни дублирование каждого элемента, ни равномерное распределение. Это нужно проверять по контракту типа, а не выводить из самого наличия makeAsyncIterator().