[Перевод] Полное руководство по модулю asyncio в Python. Часть 6

Привет, Хабр! Перед вами шестая часть (1,  2,  3,  4,  5) перевода руководства по модулю asyncio в Python. Здесь представлены 14–16 разделы исходного материала.

3745fc6be5d5c58a6d3f56686fbef335.png

14. Запуск блокирующих задач в asyncio-программах

Блокирующая задача — это такая задача, которая останавливает выполнение инструкций текущего потока.

Если в asyncio-программе запускается блокирующая задача — она останавливает весь цикл событий, не давая выполняться любым другим корутинам.

Блокирующие вызовы в asyncio-программах можно выполнять асинхронно, пользуясь функциями asyncio.to_thread() и loop.run_in_executor().

14.1. Зачем может понадобиться запуск блокирующих задач в asyncio-программах?

Суть asyncio — это асинхронное программирование и неблокирующий ввод/вывод.

Но, в то же время, в asyncio-приложениях часто нужно выполнять вызовы блокирующих функций.

Такая необходимость может возникать по множеству причин. Вот некоторые из них:

  • Для решения задачи, зависящей от производительности CPU, например — выполняющей какие-то вычисления.

  • Для выполнения блокирующей операции, зависящей от подсистемы ввода/вывода. Это может быть чтение или запись файла.

  • Для вызова функции из сторонней библиотеки, которая ещё не поддерживает механизмы модуля asyncio.

Непосредственное выполнение блокирующего вызова в asyncio-программе приведёт к остановке цикла событий на время работы этого вызова. Это не позволит другим корутинам выполняться в фоновом режиме.

Как выполнить блокирующий вызов в asyncio-программе асинхронно?

14.2. Запуск блокирующих задач

Модуль asyncio даёт нам два инструмента для выполнения блокирующих вызовов в asyncio-программах.

Первый — это функция asyncio.to_thread (). Второй — функция loop.run_in_executor ().

Функция asyncio.to_thread() — это высокоуровневый API, который рассчитан на разработчиков приложений.

Эта функция принимает имя функции, которую нужно вызвать в асинхронном режиме, а так же — аргументы, необходимые переданной ей функции.

Функция, переданная asyncio.to_thread(), выполняется в отдельном потоке. Функция asyncio.to_thread() возвращает корутину, завершения работы которой можно подождать, выполнение которой можно запланировать в виде независимой задачи.

Например:

...
выполнение функции в отдельном потоке
await asyncio.to_thread(task)

Задача, переданная asyncio.to_thread(), не начнёт выполняться до тех пор, пока возвращённой корутине не будет предоставлена возможность выполниться в цикле событий.

Внутри функции asyncio.to_thread() создаётся объект ThreadPoolExecutor для выполнения блокирующих вызовов.

В результате получается, что эта функция пригодна лишь для выполнения задач, зависящих от подсистемы ввода/вывода.

Как уже было сказано, второй подход к выполнению блокирующих вызовов в asyncio-программах заключается в применении функции loop.run_in_executor ().

Эта функция представляет низкоуровневый API asyncio, перед её применением нужно получить доступ к циклу событий. Например — с помощью функции asyncio.get_running_loop ().

Функция loop.run_in_executor() принимает исполнитель и функцию, которую нужно выполнить.

Если в качестве исполнителя ей предоставлено значение None — используется исполнитель, применяемый по умолчанию — ThreadPoolExecutor.

Функция loop.run_in_executor() возвращает объект, допускающий ожидание, завершения работы которого, при необходимости, можно подождать. Задача начнёт выполняться немедленно. Поэтому для того чтобы блокирующая задача начала бы выполняться, нет необходимости дожидаться завершения работы возвращённого объекта, допускающего ожидание, не нужно и планировать его выполнение.

Например:

...
# получение цикла событий
loop = asyncio.get_running_loop()
# выполнение функции в отдельном потоке
await loop.run_in_executor(None, task)

Как вариант, исполнитель может быть создан и передан функции loop.run_in_executor(). Она будет выполнять в этом исполнителе асинхронные вызовы.

Вызывающая сторона в таком случае отвечает за управление исполнителем, за его закрытие после завершения работы с ним.

Например:

...
# создание пула процессов
with ProcessPoolExecutor as exe:
    # получение цикла событий
    loop = asyncio.get_running_loop()
    # выполнение функции в отдельном потоке
    await loop.run_in_executor(exe, task)
    # пул процессов закрывается автоматически...

Эти два подхода позволяют выполнять в asyncio-программах блокирующие вызовы в виде асинхронных задач.

Теперь, когда мы знаем о том, как выполнять блокирующие вызовы, рассмотрим пример.

14.3. Пример выполнения задачи, зависящей от подсистемы ввода/вывода, с помощью функции asyncio.to_thread ()

Исследуем выполнение блокирующих операций ввода/вывода в asyncio-программах с применением функции asyncio.to_thread().

В этом примере мы определим функцию, которая на несколько секунд блокирует вызывающую сторону. Затем мы асинхронно выполним эту функцию в пуле потоков asyncio, пользуясь функцией asyncio.to_thread().

Это освободит вызывающую сторону, даст ей возможность выполнять корутины, решающие другие задачи.

Вот код примера:

# SuperFastPython.com
# пример выполнения блокирующей задачи, зависящей от подсистемы ввода/вывода, в asyncio
import asyncio
import time
 
# блокирующая задача, зависящая от подсистемы ввода/вывода
def blocking_task():
    # вывод сообщения
    print('Task starting')
    # блокировка на некоторое время
    time.sleep(2)
    # вывод сообщения
    print('Task done')
 
# главная корутина
async def main():
    # вывод сообщения
    print('Main running the blocking task')
    # создание корутины для блокирующей задачи
    coro = asyncio.to_thread(blocking_task)
    # планирование задачи
    task = asyncio.create_task(coro)
    # вывод сообщения
    print('Main doing other things')
    # позволяем запланированной задаче запуститься
    await asyncio.sleep(0)
    # ожидание завершения задачи
    await task
 
# запуск asyncio-программы
asyncio.run(main())

При запуске этого кода сначала создаётся корутина main(), которая играет роль точки входа в программу.

Эта корутина запускается и выводит сообщение. После этого она выполняет вызов блокирующей функции, выполнять которую планируется с помощью пула потоков. В результате этой операции возвращается корутина.

Затем возвращённую корутину оборачивают в объект Task и выполняют независимо от остального кода.

Теперь корутина main() может продолжать выполнение других задач. В данном случае она ненадолго «засыпает», позволяя запланированной задаче начать выполняться. Благодаря этому целевая функция передаётся, в недрах системы, исполнителю ThreadPoolExecutor и начинает выполняться.

Далее — корутина main() приостанавливается и ждёт завершения работы задачи.

Блокирующая функция выводит первое сообщение, «засыпает» на 2 секунды, а после этого выводит последнее сообщение.

Этот пример демонстрирует возможность асинхронного выполнения блокирующих операций ввода/вывода с использованием отдельного потока.

Main running the blocking task
Main doing other things
Task starting
Task done

Подробности о функции asyncio.to_thread() можно найти здесь.

Теперь поговорим о том, как разрабатывать и использовать асинхронные итераторы.

15. Асинхронные итераторы

Итерирование некоего объекта — это одна из базовых операций в Python.

Итерировать можно списки, строки, а так же — разные другие объекты.

Модуль asyncio позволяет разрабатывать асинхронные итераторы.

Создавать и использовать такие итераторы в asyncio-программах можно, определяя объекты, реализующие методы aiter() и anext().

Познакомимся с асинхронными итераторами поближе.

15.1. Что такое асинхронные итераторы

Асинхронный итератор — это объект, который реализует методы aiter() и anext().

Прежде чем мы ближе познакомимся с асинхронными итераторами — вспомним о том, что собой представляют итераторы классические.

Классические итераторы

Итератор — это Python-объект, который реализует специфический интерфейс.

В частности, речь идёт о методе iter(), который возвращает экземпляр итератора, и о методе next(), который инициирует выполнение одного шага итератора и возвращает значение.

Итератор: объект, представляющий поток данных. Повторяющиеся вызовы метода итератора next() (или его передача встроенной функции next ()) возвращают последовательные элементы из потока. Когда в итераторе больше не остаётся данных, вместо возврата нового значения возбуждается исключение StopIteration.

Python glossary

Обходить итератор можно с помощью встроенной в Python функции next() или — воспользовавшись циклом for.

Многие Python-объекты являются итерируемыми. В частности — речь идёт об объектах-контейнерах — таких, как списки.

Асинхронные итераторы

Асинхронный итератор — это Python-объект, реализующий специфический интерфейс.

Асинхронный итератор: объект, реализующий методы aiter() и anext().

Python glossary

Асинхронный итератор обязан реализовать методы aiter() и anext().

  • Метод aiter() должен возвращать экземпляр итератора.

  • Метод anext() должен возвращать объект, допускающий ожидание, который выполняет шаг итератора.

Пройтись по шагам асинхронного итератора, или обойти его, можно только в asyncio-программе. Например — в корутине.

Асинхронные итераторы были представлены в PEP 492 — Coroutines with async and await syntax.

Обойти асинхронный итератор можно с помощью встроенной функции anext (), которая возвращает объект, допускающий ожидание, выполняющий один шаг итератора — то есть — один вызов метода anext().

Асинхронный итератор можно обойти и используя конструкцию async for, которая автоматически вызывает anext() на каждой итерации и дожидается завершения работы полученного объекта, допускающего ожидание, чтобы получить возвращённое значение.

Асинхронный итерируемый объект способен вызывать асинхронный код в своей реализации метода iter, а асинхронный итератор может вызывать асинхронный код в своём методе next.

PEP 492 — Coroutines with async and await syntax

15.2. Что представляет собой цикл async for?

Цикл async for используется для обхода асинхронных итераторов.

Это — асинхронный вариант обычного цикла for.

Асинхронный итератор — это итератор, который выдаёт объекты, допускающие ожидание.

Напомню, что вызывающий код может ждать результатов работы объекта, допускающего ожидание, такого, как корутина или задача.

Объект, допускающий ожидание: объект, который может быть использован в выражении await.

Python glossary

Асинхронный генератор автоматически реализует методы асинхронного итератора, что позволяет итерировать его так же, как асинхронный итератор.

Конструкция async for позволяет вызывающей стороне обходить асинхронный итератор, выдающий объекты, допускающие ожидание, и получать от каждого из них результат его работы.

Это — не то же самое, что обход коллекции или списка объектов, допускающих ожидание (например — объектов корутин). Вместо этого объекты, допускающие ожидание, должны быть предоставлены с использованием методов асинхронного итератора, наличие которых ожидается у объекта, с которым ведётся работа.

Внутренние механизмы цикла async for автоматически разрешают объекты, допускающие ожидание, или ждут результатов работы таких объектов, при необходимости планируя выполнение корутин.

Так как речь идёт о разновидности цикла for — предполагается, хотя и не требуется, чтобы каждый объект, допускающий ожидание, обход которого осуществляется, вырабатывал бы возвращаемые значения.

Цикл async for необходимо использовать только внутри корутин, так как в его реализации применяется выражение await, которое можно использовать только внутри корутины.

Цикл async for можно использовать в корутинах для обхода асинхронных итераторов.

Например:

...
# обход асинхронного итератора
async for item in async_iterator:
    print(item)

Применение подобной конструкции не приведёт к распараллеливанию операций, выполняемых в цикле for. Модуль asyncio не может выполнять в одном Python-потоке больше одной корутины за раз.

Цикл async for правильнее будет воспринимать как асинхронный вариант цикла for.

Особенность асинхронного цикла заключается в том, что корутина, которая его выполняет, будет приостанавливаться и ожидать, самостоятельно пользуясь await, результатов работы каждого объекта, допускающего ожидание.

На более глубоком уровне это может означать необходимость планирования выполнения корутин и ожидания завершения их работы (это можно сказать и о задачах).

Цикл async for, кроме того, можно использовать в роли выражения в механизме спискового включения:

...
# создание списка результатов
results = [item async for item async_iterator]

Применение подобной конструкции приведёт к созданию списка возвращённых значений, полученных из асинхронного итератора.

Теперь поговорим о том, как определять, создавать и использовать асинхронные итераторы.

15.3. Как пользоваться асинхронными итераторами

Здесь мы поговорим о том, как определять и создавать асинхронные итераторы, как добиваться от них получения очередного значения и как их обходить.

Начнём с определения асинхронного итератора.

Определение асинхронного итератора

Асинхронный итератор можно определить, определив класс, который реализует методы aiter() и anext().

Эти методы определяются в объекте так же, как и любые другие методы.

Тут важно обратить внимание на то, что, так как функция anext() должна возвращать объект, допускающий ожидание, в её определении необходимо использовать конструкцию async def.

object.anext(self): должен возвращать объект, допускающий ожидание, что приводит к выдаче следующего значения итератора. Этот метод должен вызывать ошибку в том случае, если значения в итераторе закончились.

Asynchronous iterators

Когда итератору нечего больше возвращать — метод anext() должен вызывать исключение StopAsyncIteration.

Например:

# определение асинхронного итератора
class AsyncIterator():
    # конструктор, объявление некоторых данных, относящихся к состоянию объекта
    def __init__(self):
        self.counter = 0
 
    # создание экземпляра итератора
    def __aiter__(self):
        return self
 
    # возврат следующего объекта, допускающего ожидание
    async def __anext__(self):
        # проверка на то, есть ли ещё невозвращённые элементы
        if self.counter >= 10:
            raise StopAsyncIteration
        # инкрементирование счётчика
        self.counter += 1
        # возврат значения счётчика
        return self.counter

Асинхронный итератор — это корутина. Такие итераторы возвращают объекты, допускающие ожидание, которые планируются на выполнение и выполняются в цикле событий asyncio. Поэтому можно выполнять объекты, допускающие ожидание, и дожидаться их результатов в пределах тела итератора.

Например:

...
# возврат следующего объекта, допускающего ожидание
async def __anext__(self):
    # проверка на то, есть ли ещё невозвращённые элементы
    if self.counter >= 10:
        raise StopAsyncIteration
    # инкрементирование счётчика
    self.counter += 1
    # имитация работы
    await asyncio.sleep(1)
    # возврат значения счётчика
    return self.counter

Теперь поговорим об использовании асинхронных итераторов.

Создание асинхронного итератора

Прежде чем воспользоваться асинхронным итератором — надо создать его экземпляр.

Это выглядит так же, как и создание обычных Python-объектов:

...
# создание итератора
it = AsyncIterator()

В ходе работы этого кода возвращается «асинхронный итерируемый объект», который является экземпляром «асинхронного итератора».

Получение очередного значения асинхронного итератора

Для того чтобы итератор выполнил бы один шаг и возвратил бы очередное значение — можно воспользоваться встроенной функцией anext(). Выглядит это так же, как применение функции next() при работе с обычным итератором.

Результат одного шага итератора получают, дождавшись завершения работы объекта, допускающего ожидание.

Например:

...
# получить объект, допускающий ожидание, выполнив один шаг итератора
awaitable = anext(it)
# выполнить один шаг итератора и получить результат
result = await awaitable

Эти две операции можно выполнить в одну строку:

...
# выполнить шаг асинхронного итератора
result = await anext(it)

Обход асинхронного итератора

Асинхронный итератор можно обойти в цикле, воспользовавшись конструкцией async for, которая автоматически организует ожидание получения результатов каждого шага итератора.

Например:

...
# обход асинхронного итератора
async for result in AsyncIterator():
    print(result)

Подробности о цикле async for можно найти здесь.

Цикл async for можно использовать и в асинхронном варианте механизма спискового включения для сбора результатов работы итератора:

...
# асинхронный вариант механизма спискового включения с применением асинхронного итератора
results = [item async for item in AsyncIterator()]

15.4. Пример работы с асинхронным итератором

Исследуем работу с асинхронными итераторами с применением цикла async for.

Здесь представлен доработанный вариант примера из предыдущего раздела. Обход итератора в данном случае осуществляется с помощью цикла async for.

Этот цикл автоматически дожидается результатов работы каждого объекта, допускающего ожидание, возвращённого из итератора. Далее, он получает возвращаемое значение, которое оказывается доступным в теле цикла, что позволяет его вывести.

Это, вероятно, наиболее часто встречающийся паттерн использования асинхронных итераторов.

Вот полный код примера:

# SuperFastPython.com
# пример работы с асинхронным итератором с применением цикла async for
import asyncio
 
# определение асинхронного итератора
class AsyncIterator():
    # конструктор, объявление некоторых данных, относящихся к состоянию объекта
    def __init__(self):
        self.counter = 0
 
    # создание экземпляра итератора
    def __aiter__(self):
        return self
 
    # возврат следующего объекта, допускающего ожидание
    async def __anext__(self):
        # проверка на то, есть ли ещё невозвращённые элементы
        if self.counter >= 10:
            raise StopAsyncIteration
        # инкрементирование счётчика
        self.counter += 1
        # имитация работы
        await asyncio.sleep(1)
        # возврат значения счётчика
        return self.counter
 
# главная корутина
async def main():
    # обход асинхронного итератора с помощью цикла async for
    async for item in AsyncIterator():
        print(item)
 
# выполнение asyncio-программы
asyncio.run(main())

Здесь сначала создаётся корутина main(), используемая как точка входа в asyncio-программу. В ходе работы этой корутины запускается асинхронный цикл for.

Далее — создаётся экземпляр асинхронного итератора, после чего цикл автоматически получает его элементы, используя, для возврата объектов, допускающих ожидание, функцию anext(). После получения очередного элемента, цикл ожидает результатов его выполнения и получает значение, работать с которым можно в коде тела цикла. Это значение выводится в сообщении.

Этот процесс повторяется. Корутина main() приостанавливается, выполняется очередной шаг итератора, который тоже приостанавливается, после чего возобновляется работа main(). Так происходит до тех пор, пока итератор не выдаст все имеющиеся у него значения.

После того, как внутренний счётчик итератора дойдёт до 10 — вызывается исключение StopAsyncIteration. Но это не приводит к аварийному завершению работы программы. Вместо этого система ожидает появления этого исключения. Оно обрабатывается циклом async for, после чего цикл прекращается.

Здесь был продемонстрирован обход асинхронного итератора с использованием цикла async for.

1
2
3
4
5
6
7
8
9
10

Подробности об асинхронных итераторах ищите здесь.

Наша следующая тема — асинхронные генераторы.

16. Асинхронные генераторы

Генераторы — это неотъемлемая часть Python.

Генератор — это функция, имеющая как минимум одно выражение yield. Выполнение таких функций, как и корутин, можно приостанавливать и возобновлять.

На самом деле, Python-корутины — это расширение Python-генераторов.

Модуль asyncio позволяет разрабатывать асинхронные генераторы.

Асинхронный генератор можно создать, определив корутину, которая использует выражение yield.

Поговорим об асинхронных генераторах.

16.1. Что такое асинхронные генераторы

Асинхронный генератор — это корутина, в которой используется выражение yield. Прежде чем мы углубимся в исследование асинхронных генераторов — вспомним о том, что собой представляют классические Python-генераторы.

Классические генераторы

Генератор — это Python-функция, которая возвращает значения посредством выражения yield:

# определение генератора
def generator():
    for i in range(10):
        yield i

Код генератора выполняется до выражения yield, после чего возвращается значение. В этом месте выполнение генератора приостанавливается. Когда генератор будет выполнен в следующий раз — его работа возобновится там, где была приостановлена, после чего его код будет выполняться до следующего выражения yield.

Генератор: функция, которая возвращает итерируемый генератор. Он выглядит как обычная функция, за исключением того, что содержит выражение yield, с помощью которого выдаются последовательности значений. Их можно применять в циклах for или получать по одному, пользуясь функцией next ().

Python glossary

С технической точки зрения, функция-генератор создаёт и возвращает итерируемый генератор. Он выполняет код функции, при необходимости выдавая значения, приостанавливая и возобновляя её работу.

Итерируемый генератор: объект, созданный функцией-генератором. Каждое выражение yield временно приостанавливает его работу, при этом запоминается состояние выполнения в том месте, где была приостановлена работа […]. Когда итерируемый генератор возобновляет работу — он начинает с того места, где остановился.

Python glossary

Пошаговое выполнение кода генератора можно организовать, воспользовавшись встроенной функцией next ().

Например:

...
# создание генератора
gen = generator()
# получение следующего значения генератора
result = next(gen)

Правда, чаще генераторы итерируют до тех пор, пока они не перестают выдавать значения. Например — используя цикл for или механизм спискового включения:

...
# обход генератора и сбор результатов
results = [item for item in generator()]

А теперь вернёмся к асинхронным генераторам.

Асинхронные генераторы

Асинхронный генератор — это корутина, которая использует выражение yield.

В отличие от функции-генератора, корутина может планировать и ожидать выполнение других корутин и задач.

Асинхронный генератор: функция, которая возвращает асинхронный итерируемый генератор. Она похожа на функцию корутины, определённую с использованием ключевых слов async def, за исключением того, что она содержит выражение yield. Это выражение используется для выдачи последовательности значений, подходящих для использования в цикле async for.

Python glossary

Функции асинхронных генераторов, как и функции генераторов классических, могут использоваться для создания асинхронных итерируемых генераторов. Их можно обходить с помощью встроенной функции anext(), классический аналог которой носит имя next().

Асинхронный итерируемый генератор: объект, созданный асинхронной функцией-генератором. Это — асинхронный итератор, который, при обращении к нему с использованием метода anext(), возвращает объект, допускающий ожидание, выполняющий тело асинхронной функции-генератора до следующего выражения yield.

Python glossary

Это значит, что асинхронный итерируемый генератор реализует метод anext(), и что с ним можно работать в цикле async for.

Каждая итерация генератора планируется и выполняется в виде объекта, допускающего ожидание. Цикл async for запланирует и выполнит каждую итерацию генератора, приостанавливая вызывающую корутину и ожидая результат.

Подробности об async for можно найти здесь.

16.2. Использование асинхронных генераторов

Здесь мы поговорим о том, как определять и создавать асинхронные генераторы, как получать их значения и как их обходить.

Начнём с их определения.

Определение асинхронного генератора

Определить асинхронный генератор можно, определив корутину, в которой имеется по меньшей мере одно выражение yield.

Это значит, что интересующая нас функция определяется с использованием ключевых слов async def.

Например:

# определение асинхронного генератора
async def async_generator():
    for i in range(10)
        yield i

Асинхронный генератор — это корутина. Каждый итератор возвращает объект, допускающий ожидание, выполнение которого планируется в цикле событий asyncio, и который в этом цикле выполняется. Поэтому мы можем выполнять такие объекты и ожидать результаты их работы в теле генератора.

Например:

# определение асинхронного генератора, в котором используется выражение await
async def async_generator():
    for i in range(10)
        # приостановка работы
        await asyncio.sleep(1)
        # выдача значения вызывающей стороне
        yield i

Теперь поговорим об использовании асинхронных генераторов.

Создание асинхронного генератора

Для использования асинхронного генератора сначала надо создать его экземпляр.

Это выглядит как вызов соответствующей функции, которая создаёт и возвращает объект итератора:

...
# создание итератора
it = async_generator()

В ходе выполнения этого кода возвращается значение, представляющее собой асинхронный итератор, имеющее тип, соответствующий асинхронному итерируемому генератору.

Получение очередного значения асинхронного генератора

Продвинуть генератор на один шаг можно, воспользовавшись встроенной функцией anext (). Это похоже на то, как следующее значение классического генератора получают с помощью функции next().

Результатом этого вызова является объект, допускающий ожидание. Поэтому для того чтобы получить значение, которое он выдаёт, нужно подождать завершения его работы.

Например:

...
# получить объект, допускающий ожидание, для одного шага генератора
awaitable = anext(gen)
# выполнить полученный объект и получить результат
result = await awaitable

То же самое можно записать и в одну строку:

...
# один шаг асинхронного генератора
result = await anext(gen)

Обход асинхронного генератора

Асинхронные генераторы можно обходить в циклах, пользуясь конструкцией async for, которая, на каждой итерации цикла, автоматически будет ожидать готовности результата.

Например:

...
# обход асинхронного генератора
async for result in async_generator():
    print(result)

Для обхода асинхронных генераторов можно воспользоваться и асинхронным механизмом спискового включения, прибегнув к циклу async for для сбора результатов, выдаваемых генератором:

...
# асинхронный механизм спискового включения и асинхронный генератор
results = [item async for item in async_generator()]

16.3. Пример работы с асинхронным генератором

Исследуем обход асинхронного генератора с использованием цикла async for.

В этом примере, основанном на предыдущем примере, мы рассмотрим полный обход генератора с применением async for.

Цикл будет автоматически ожидать разрешения каждого объекта, допускающего ожидание, возвращённого из генератора. Полученные значения будут доступны в теле цикла, что, в данном случае, означает возможность их вывода в сообщениях.

Это, вероятно, пример реализации того сценария, в котором асинхронные генераторы используются чаще всего.

Вот полный код примера:

# SuperFastPython.com
# пример использования асинхронного генератора в цикле async for
import asyncio
 
# определение асинхронного генератора
async def async_generator():
    # обычный цикл
    for i in range(10):
        # блокировка, имитирующая выполнение работы
        await asyncio.sleep(1)
        # выдача результата
        yield i
 
# главная корутина
async def main():
    # обход асинхронного генератора с помощью цикла async for
    async for item in async_generator():
        print(item)
 
# выполнение asyncio-программы
asyncio.run(main())

Здесь сначала создаётся корутина main(), представляющая собой точку входа в программу. Эта корутина начинает работу и запускает цикл async for.

Создаётся экземпляр асинхронного генератора, а цикл автоматически осуществляет получение его значений, пользуясь встроенной функцией anext() для получения объектов, допускающих ожидание. Получив такой объект, цикл дожидается завершения его работы и получает его значение, с которым можно работать в теле цикла. Это значение выводится на экран.

Этот процесс повторяется до тех пор, пока в генераторе не закончатся значения. Это выглядит так: корутина main() приостанавливается, выполняется одна итерация генератора, он приостанавливается, возобновляется выполнение main().

Данный пример демонстрирует обход асинхронного генератора с использованием цикла async for.

0
1
2
3
4
5
6
7
8
9

Здесь можно найти подробности об асинхронных генераторах.

В следующем разделе изучим асинхронные менеджеры контекста.

О, а приходите к нам работать?

© Habrahabr.ru