Зачем нужны Асинхронные итераторы в Python?

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

Вывод:

async
wait for Andrey 2c
wait for Alex 2c
wait for Artem 2c
Time: 6.04335618019104

No async
wait for Andrey 2c
wait for Alex 2c
wait for Artem 2c
Time: 6.024480581283569
import asyncio
import time


class Iterator:
    def __init__(self, delay, people: list[str]):
        self._people = people
        self.delay = delay
        self._i = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self._i >= len(self._people):
            raise StopIteration

        human = self._people[self._i]
        self._i += 1

        time.sleep(self.delay)

        return f"wait for {human} {self.delay}c"


class Crowd:
    def __init__(self, delay, people: list[str]):
        self._people = people
        self.delay = delay
        self._i = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self._i >= len(self._people):
            raise StopAsyncIteration

        human = self._people[self._i]
        self._i += 1

        await asyncio.sleep(self.delay)

        return f"wait for {human} {self.delay}c"


async def amain():
    crowd = Crowd(2, ["Andrey", "Alex", "Artem"])
    async for i in crowd:
        print(i)


def main():
    iterr = Iterator(2, ["Andrey", "Alex", "Artem"])
    for i in iterr:
        print(i)


begin = time.time()

print("async")
asyncio.run(amain())

print("Time:", time.time() - begin)
begin = time.time()

print("\nNo async")
main()

print("Time:", time.time() - begin)

В моём понимании при первой асинхронной итерации должен вызваться метод __anext__ и дойдя до await asyncio.sleep(delay) должна происходить следующая итерация, так как asyncio.sleep(delay) передаёт управление следующим task-ам (как в примере ниже), но этого не происходит. Вопрос: зачем тогда нужен асинхронный итератор, если обычный итератор даёт такие же результаты по времени?

async def sleep(i):
    print(f"({i}) start")
    await asyncio.sleep(2)


async def main():
    tasks = [sleep(i) for i in range(1, 6)]
    await asyncio.gather(*tasks)


asyncio.run(main())

Ответы (1 шт):

Автор решения: CrazyElf

Короткий пример с вашим же кодом - асинхронное одновременное выполнение двадцати ваших итераторов за тоже самое время, что выполняется один итератор:

# asyncio.run(amain())
asyncio.run(asyncio.gather(*[amain() for _ in range(20)]))

Если у вас остальной код будет асинхронный, то использование асинхронных конструкций позволит выполнять код [почти] параллельно, причём это возможно даже без использования дополнительных потоков, даже в одном потоке. Пока в одном месте производится асинхронное ожидание ввода-вывода или работает асинхронный sleep, в другом месте программы может выполняться другой код. В моём примере параллельно выполняются 20 веток кода, поскольку код в основном спит всё это время, это практически не тратит ресурсы процессора.

→ Ссылка