В ответ на ваш комментарий:
... [T] Я понимаю, что когда я звоню main()
через asyncio.run()
, это создает своего рода «обертку» или объект, который отслеживает поток и пытается выполнить вычисления, когда асинхронная функция простаивает
(Небольшой отказ от ответственности - почти весь мой опыт с асинхронными c материалами находится в C#, но ключевые слова await
в каждом из них выглядят довольно хорошо по поведению)
Ваше понимание здесь более или менее правильно - asyncio.run(main())
запустит main()
в отдельной (фоновой) "теме".
( Примечание: здесь я игнорирую специфику GPL и однопоточности python. Для пояснения достаточно «отдельного потока». )
Недоразумение возникает из-за того, как вы думаете, await
работает, как оно на самом деле работает и как вы организовали свой код. Я действительно не смог найти достаточного описания того, как Python await
работает, кроме как в PEP , который представил его:
await, аналогично yield из приостанавливает выполнение сопрограммы read_data до тех пор, пока ожидаемое завершение db.fetch не вернет данные результата.
С другой стороны, C# await
имеет намного больше документация / объяснение , связанное с ним:
Когда применяется ключевое слово await, оно приостанавливает вызывающий метод и возвращает управление вызывающей стороне до тех пор, пока ожидаемая задача не будет завершено.
В вашем случае «средний выход» (3 и 4) предшествовал 2 ожиданиям, оба из которых вернут управление на asycnio.run(...)
, пока не получат результат.
Вот код, который я использовал и который дает мне результат, который вы ищете:
import asyncio
async def part1():
print('1')
await asyncio.sleep(2)
print('5')
async def part2():
print('2')
await asyncio.sleep(2)
print('6')
async def part3():
print('3')
print('4')
async def main():
t1 = asyncio.create_task(part1())
t2 = asyncio.create_task(part2())
t3 = asyncio.create_task(part3())
await t1
await t2
await t3
asyncio.run(main())
Вы заметите, что я превратил ваш main
в мой part3
и создал новый main
. В новом main
я создаю отдельный ожидаемый Task
для каждой части (1, 2 и 3). Затем я await
последовательно.
Когда t1
выполняется, он достигает await
после первого отпечатка. Это делает паузу part1
в этой точке, пока ожидаемое не завершится. Управление программой вернется к вызывающей стороне (main
) до этой точки, подобно тому, как работает yield
.
Пока t1
находится в режиме "паузы" (ожидания), main
будет продолжаться и запускаться до t2
. t2
делает то же самое, что и t1
, поэтому вскоре после этого будет запущен t3
. t3
не выполняет await
-ing, поэтому его вывод происходит немедленно.
В этот момент main
просто ожидает, пока его дочерний элемент Task
s не завершится sh up. t1
был await
-ed первым, поэтому он вернется первым, а затем t2
. Конечный результат (где test.py
- сценарий, в который я поместил это):
~/.../> py .\test.py
1
2
3
4
5
6