Проблема с потоком выполнения asyncio

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

Что я сделал, так это то, что в функции outer я создал асинхронные задачи и сохранил их в массиве tasks. Прежде чем ждать выполнения этих задач, я написал оператор печати print("outer"), который должен выполняться на каждой итерации. И внутри задачи я написал еще один оператор печати print("inner") в функции inner. Но каким-то образом я получаю неожиданный результат.

Вот код -

import asyncio


def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(outer(loop))
    loop.close()


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        await task


async def inner():
    print("inner")
    await asyncio.sleep(0.5)

if __name__ == '__main__':
    main()

Вот результат -

outer
inner
inner
inner
inner
inner
outer
outer
outer
outer

Мой ожидаемый результат был -

outer
inner
outer
inner
outer
inner
outer
inner
outer
inner

Почему все inner печатают до outer. Каков правильный поток выполнения asyncio. Заранее спасибо.


person shikhar srivastava    schedule 29.03.2020    source источник
comment
Каждая задача / сопрограмма выполняется до оператора await, после чего она приостанавливается. Ваш пример аналогичен пример сопрограммы в документации, дающей аналогичные результаты.   -  person wwii    schedule 29.03.2020
comment
Из документации - While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task.   -  person wwii    schedule 29.03.2020
comment
Но когда ожидается выполнение одной задачи, то для запуска новой задачи цикл обработки событий должен выполнить следующую for итерацию цикла. Если это правда, то нужно было напечатать outer.   -  person shikhar srivastava    schedule 30.03.2020


Ответы (2)


In async def outer(loop)

for i in range(0, 5):
    tasks.append(loop.create_task(inner()))
  • Созданы, запланированы и запущены пять новых inner задач.
  • цикл событий запускает запланированные задачи до их завершения.

Если вы добавите немного больше к inner и outer, это лучше покажет этот процесс:

async def outer(loop):
    tasks = []
    for i in range(0, 5):
        tasks.append(loop.create_task(inner(i)))
    await asyncio.sleep(3)
    for task in tasks:
        print('outer')
        await task

async def inner(n):
    print(f"inner {n} start")
    await asyncio.sleep(0.5)
    print(f'inner {n} end')
  • пока outer спит

    • The first task runs to its await statement
    • Цикл событий приостанавливает выполнение первой задачи
    • Цикл событий запускает следующую запланированную задачу до ее оператора ожидания.
    • Это продолжается до тех пор, пока у каждой задачи не будет возможности подойти к своему оператору await, после чего цикл обработки событий начинает осматриваться, чтобы увидеть, выполнены ли какие-либо задачи в ожидании того, чего они ждали, - если это сделано, это позволяет им выполнить еще немного.
    • Это продолжается до тех пор, пока все задачи не будут выполнены.
  • Вы можете видеть, что пять задач выполняются и заканчиваются до того, как начнется второй цикл for.

  • во втором цикле for каждая задача уже завершена, поэтому await task нечего ждать, а outer печатается пять раз подряд.

Я немного не уверен в том, что цикл событий управляет всем - я не нашел для этого явной документации - вероятно, упоминается в create_task документах: Wrap the coro coroutine into a Task and schedule its execution. Когда вы создаете задачу, она становится запланированной. Я видел видео на pyvideo.org, которые демонстрируют этот процесс, но, к сожалению, я не смог быстро найти тот, на который хотел бы сделать ссылку.

person wwii    schedule 30.03.2020
comment
Вы нашли ссылку на pyvideo.org, где объясняется весь процесс? - person shikhar srivastava; 19.02.2021
comment
@shikharsrivastava это ускользает от меня. Их слишком много, чтобы их посмотреть / пересмотреть, чтобы найти. Возможно, я даже не помню части многих - думая, что они одно целое. - person wwii; 19.02.2021

loop.create_task(inner()) немедленно ставит в очередь на выполнение все 5 inner задач, а не await task. await task приостанавливает outer до тех пор, пока первая задача не будет полностью выполнена, то есть не менее чем на 0,5 секунды. При этом все inner задачи были запущены один раз await, включая их print.


async def outer(loop):
    tasks = []
    for i in range(0, 5):
        # queues task immediately
        tasks.append(loop.create_task(inner()))

    for task in tasks:
        print("outer")
        # suspends for at least 0.5 seconds
        await task


async def inner():
    # runs immediately
    print("inner")
    await asyncio.sleep(0.5)
person MisterMiyagi    schedule 30.03.2020
comment
Ага!! Если вы добавите await asyncio.sleep(10); print('outer done sleeping' сразу после цикла, который создает задачи, вы увидите, что они выполняются до того, как ожидаются какие-либо. спасибо - person wwii; 30.03.2020