Почему в большинстве примеров asyncio используется loop.run_until_complete()?

#python-3.x #python-asyncio

#python-3.x #python-asyncio

Вопрос:

Я просматривал документацию Python для asyncio и мне интересно, почему в большинстве примеров используется loop.run_until_complete() в отличие от Asyncio.ensure_future() .

Например:https://docs.python.org/dev/library/asyncio-task.html

Кажется, ensure_future это был бы гораздо лучший способ продемонстрировать преимущества неблокирующих функций. run_until_complete с другой стороны, блокирует цикл, как это делают синхронные функции.

Это заставляет меня чувствовать, что я должен использовать run_until_complete вместо комбинации ensure_future with loop.run_forever() для одновременного запуска нескольких подпрограмм.

Комментарии:

1. run_until_complete ничего не блокирует. Разница между ним и run_forever заключается в том, что цикл приостанавливается при завершении сопрограммы. Единственный раз, когда он будет заблокирован, — это если ваша сопрограмма никогда не ожидает.

2. Я написал это pastebin.com/Qi8dQ3bh и, похоже, это блокирует цикл. do_other_things() не выполняется до do_io() завершения, даже если do_io() ожидает перехода в режим ожидания.

3. Это потому, что в цикле больше ничего не было запланировано. Попробуйте вызвать loop.create_task(do_other_things()) перед вызовом run_forever .

Ответ №1:

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

Учитывая этот пример:

 import asyncio

async def do_io():
    print('io start')
    await asyncio.sleep(5)
    print('io end')

async def do_other_things():
    print('doing other things')

loop = asyncio.get_event_loop()

loop.run_until_complete(do_io())
loop.run_until_complete(do_other_things())

loop.close()
  

do_io будет выполняться. После его завершения do_other_things запустится. Ваш вывод будет:

 io start
io end
doing other things
  

Если вы запланируете do_other_things цикл событий перед запуском do_io , управление переключится с do_io на do_other_things , когда первый ожидает.

 loop.create_task(do_other_things())
loop.run_until_complete(do_io())
  

Это даст вам результат:

 doing other things
io start
io end
  

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

Комментарии:

1. Вы знаете, почему я получаю ошибку «RuntimeError: этот цикл событий уже запущен» при запуске вашего кода?

2. @Patrick ты пробовал вызывать loop.run_until_complete изнутри функции?

3. Я понял, что проблема, возможно, с Jupyter. Это работает с кодом python.

4. @Patrick (поздно) но у меня есть сцена this и используется loop = asyncio.new_event_loop() (хотя разрешен только один цикл событий в main)

5. get_event_loop не рекомендуется: docs.python.org/3/library /…

Ответ №2:

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

 async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello')) # not block here

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}") # time0

    await task1 # block here!

    print(f"finished at {time.strftime('%X')}") 
    
    await task2 # block here!

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())
  

результат

 time0 
print hello 
time0 1 
print world 
time0 2
  

но ЕСЛИ ВЫ НЕ ОЖИДАЕТЕ task1, сделайте что-нибудь еще

 async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello')) # not block here
    print(f"finished at {time.strftime('%X')}") # time0
    await asyncio.sleep(2) # not await task1
    print(f"finished at {time.strftime('%X')}") # time0 2
 

asyncio.run(main())
  

он ВСЕ еще выполняет задачу 1

 time0
print hello
time0 2
  

Комментарии:

1. Хорошо, не забудьте импортировать время импорта asyncio

2. Этот ответ хорош, потому что он использует современный Python 3.10 asyncio.create_task/asyncio.run. Сведение его только к этим двум вместе с asyncio.gather, asyncio.sleep и async / await сами по себе значительно упрощают.