Async для цикла в AsyncGenerator - PullRequest
0 голосов
/ 17 октября 2018

Имея асинхронный генератор, я ожидал бы, что смогу перебирать его асинхронно.Тем не менее, я что-то упускаю или что-то путаю, или и то, и другое, так как в конце я получаю обычный синхронный цикл for:

import asyncio


async def time_consuming(t):
    print(f"Going to sleep for {t} seconds")
    await asyncio.sleep(t)
    print(f"Slept {t} seconds")
    return t


async def generator():
    for i in range(4, 0, -1):
        yield await time_consuming(i)


async def consumer():
    async for t in generator():
        print(f"Doing something with {t}")


if __name__ == '__main__':
    loop = asyncio.new_event_loop()
    loop.run_until_complete(consumer())
    loop.close()

Это займет около 12 секунд, чтобы запустить и вернуть это:

Going to sleep for 4 seconds
Slept 4 seconds
Doing something with 4
Going to sleep for 3 seconds
Slept 3 seconds
Doing something with 3
Going to sleep for 2 seconds
Slept 2 seconds
Doing something with 2
Going to sleep for 1 seconds
Slept 1 seconds
Doing something with 1

Хотя я ожидал, что это займет около 4 секунд, чтобы запустить и вернуть что-то вроде этого:

Going to sleep for 4 seconds
Going to sleep for 3 seconds
Going to sleep for 2 seconds
Going to sleep for 1 seconds
Slept 4 seconds
Doing something with 4
Slept 3 seconds
Doing something with 3
Slept 2 seconds
Doing something with 2
Slept 1 seconds
Doing something with 1

1 Ответ

0 голосов
/ 17 октября 2018

Асинхронный генератор не означает, что вы выполняете итерацию одновременно!Все, что вы получаете, это больше мест для сопрограммы, чтобы уступить другим задачам.Шаги итерации по-прежнему выполняются последовательно .

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

Если вы ожидали каждогоОтдельный шаг вашего генератора должен выполняться одновременно, тогда вам все равно придется запланировать дополнительные задачи, явно , с циклом событий.

Затем вы можете вернуться из генератора, когда все эти дополнительныезадачи выполнены.Если вы запланировали свои 4 time_consuming() сопрограммы как задачи, используйте asyncio.wait(), чтобы дождаться завершения одной или всех задач и получить результаты от выполненных задач, тогда да, после вашей for i in range(...): цикл завершен, ваш процесс займет всего 4 секунды:

async def generator():
    pending = []
    for i in range(4, 0, -1):
        pending.append(asyncio.create_task(time_consuming(i)))

    while pending:
        done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
        for task in done:
            yield task.result()

, в этот момент вывод будет

Going to sleep for 4 seconds
Going to sleep for 3 seconds
Going to sleep for 2 seconds
Going to sleep for 1 seconds
Slept 1 seconds
Doing something with 1
Slept 2 seconds
Doing something with 2
Slept 3 seconds
Doing something with 3
Slept 4 seconds
Doing something with 4

Обратите внимание, что это реверс Порядок из ожидаемых результатов, потому что для этого нужны результаты задачи , поскольку они завершают , а не ожидают завершения создания первой задачи.Обычно это то, что вы хотите, правда.Зачем ждать 4 секунды, когда у вас уже есть готовый результат после 1?

У вас тоже может быть свой вариант, но вы просто по-другому его кодируете.Затем вы можете просто использовать asyncio.gather() для 4 задач , который планирует запуск сопрограмм как параллельных задач, и возвращать их результаты в виде списка, после чего вы можете получить эти результаты:

async def generator():
    tasks = []
    for i in range(4, 0, -1):
        tasks.append(time_consuming(i))

    for res in await asyncio.gather(*tasks):
        yield res 

, но теперь вывод становится

Going to sleep for 4 seconds
Going to sleep for 3 seconds
Going to sleep for 2 seconds
Going to sleep for 1 seconds
Slept 1 seconds
Slept 2 seconds
Slept 3 seconds
Slept 4 seconds
Doing something with 4
Doing something with 3
Doing something with 2
Doing something with 1

, потому что мы не можем ничего делать до тех пор, пока самая длинная задача, time_consuming(4), не будет завершена, но более короткие задачи завершатся до этой точкии уже выведите свое сообщение Slept ... seconds.

...