Сервису требуется передать результат от одной корутины другой без блокировки event loop. Как asyncio.Future обеспечивает такую передачу?
asyncio.Future хранит ещё не готовый результат и уведомляет ожидающую корутину, когда результат установлен. Корутина, выполняющая await future, приостанавливается и освобождает event loop, а после вызова set_result, set_exception или cancel возобновляется.
Future не выполняет работу самостоятельно: это контейнер состояния и механизм одноразовой доставки результата между частями асинхронной программы.
В асинхронных системах результат операции часто появляется позже, чем запускается сама операция. Нужен объект, который можно вернуть вызывающему коду заранее, а затем заполнить результатом при завершении работы.
Future решает именно эту задачу: отделяет момент создания ожидания от момента получения результата. В asyncio он стал базовым примитивом для адаптеров ввода-вывода, callback-механизмов и низкоуровневого взаимодействия с event loop.
Если корутина будет ждать результат обычным блокирующим способом, она остановит поток, в котором работает event loop. Это помешает выполнению других задач и может привести к задержкам всего сервиса.
Кроме того, передача результата через общую переменную требует самостоятельно организовывать уведомление о готовности данных. Ошибки в такой логике приводят к преждевременному чтению значения, зависанию или потере исключения.
Future имеет состояние: обычно он ожидает результат, а после завершения переходит в одно из конечных состояний — результат установлен, исключение установлено или объект отменён. Установить конечное состояние можно только один раз.
Когда задача выполняет await future, event loop регистрирует продолжение этой задачи и передаёт управление другим готовым задачам. После завершения Future event loop планирует ожидающую задачу для дальнейшего выполнения.
Здесь consumer не занимает поток во время ожидания. Через секунду producer вызывает set_result, после чего consumer продолжает выполнение и получает строку "готово".
Если операция завершилась ошибкой, следует вызвать set_exception; тогда await future завершится соответствующим исключением. Если Future отменён, ожидающая задача получит asyncio.CancelledError.
Future рассчитан на один результат, а не на поток сообщений. Для нескольких элементов обычно подходит asyncio.Queue, для простого сигнала без данных — asyncio.Event. Future также не является общим потокобезопасным примитивом: устанавливать его состояние из другого потока напрямую нельзя; для этого используют планирование callback в event loop через loop.call_soon_threadsafe.
В HTTP-клиенте низкоуровневый callback получает уведомление о завершении операции, а код высокого уровня должен асинхронно дождаться ответа. Временная общая переменная с флагом потребовала бы цикла проверки или отдельной блокировки, что усложнило бы код и могло бы задерживать event loop.
Можно применить Event, но он не переносит сам результат и требует отдельного хранилища. Queue передаёт данные надёжно, однако добавляет семантику очереди и избыточен для единственного ответа.
Выбором становится Future: callback устанавливает результат ровно один раз, а ожидающая корутина получает его через await. Это сохраняет неблокирующее выполнение и явно связывает одну асинхронную операцию с одним результатом.
Можно ли нескольким корутинам ожидать один и тот же Future?
Да. Несколько корутин могут выполнить await для одного Future. После установки результата event loop возобновит всех ожидающих, и каждая получит одно и то же значение.
Это не превращает Future в очередь: элементы не распределяются между потребителями, а результат становится доступным всем ожидающим. Поэтому Future подходит для публикации одного общего результата, но не для последовательной передачи множества сообщений.
Что произойдёт, если вызвать set_result у уже завершённого Future?
Будет выброшено исключение asyncio.InvalidStateError. Future нельзя переиспользовать для нового результата после set_result, set_exception или cancel.
Если завершение может произойти из нескольких ветвей, каждая ветвь должна проверять состояние либо использовать архитектуру, гарантирующую единственного владельца завершения. Простая повторная запись результата не является допустимой операцией.
Что происходит с Future при отмене задачи, которая его ожидает?
Обычно отмена ожидающей задачи распространяется на непосредственно ожидаемый Future: он также становится отменённым. Если Future нельзя отменять вместе с внешней задачей, ожидание защищают через asyncio.shield.
При этом shield не делает саму операцию неуязвимой для любых отмен: он отделяет внутренний Future от отмены внешней задачи. Внешний await всё равно может завершиться отменой, а внутренняя операция продолжит работу, если её отдельно не отменили.