asyncio中事件循环如何调度任务?
关于Asyncio程序输出不如预期
大家好。我最近在研究asyncio,想了解事件循环、任务、协程等是怎么工作的。从这个和这个讨论中,我了解到任务在遇到await
关键字时并不会把控制权交还给事件循环,而是在遇到yield
时才会交还。然后事件循环会去寻找其他已经提交的任务并执行它们。说了这么多,我有一个测试程序如下:
import asyncio
from datetime import datetime
async def a():
print(f"start a {datetime.now()}")
await asyncio.sleep(0)
print(f"done a {datetime.now()}")
async def b():
print(f"start b {datetime.now()}")
await asyncio.sleep(0)
print(f"done b {datetime.now()}")
async def main():
print(f"time start main: {datetime.now()}")
a_result = asyncio.create_task(a())
b_result = asyncio.create_task(b())
# await a_result
# await b_result
print(asyncio.all_tasks())
print(f"time done main: {datetime.now()}")
if __name__ == "__main__":
asyncio.run(main())
print(f"back to main: {datetime.now()}")
输出结果是:
time start main: 2024-03-17 11:24:53.119904
{<Task pending name='Task-1' coro=<main() running at /home/nam/practice/main.py:24> cb=[_run_until_complete_cb() at /usr/lib/python3.10/asyncio/base_events.py:184]>, <Task pending name='Task-2' coro=<a() running at /home/nam/practice/main.py:4>>, <Task pending name='Task-3' coro=<b() running at /home/nam/practice/main.py:10>>}
time done main: 2024-03-17 11:24:53.120038
start a 2024-03-17 11:24:53.120076
start b 2024-03-17 11:24:53.120110
back to main: 2024-03-17 11:24:53.120266
我原本期待程序的执行顺序是这样的:
- 进入
asyncio.run()
的入口 - 打印出主函数的开始时间
- 创建(但不执行)包装协程
a
和b
的任务 - 打印出任务列表
- 打印出主函数的结束时间
- 打印“回到主函数”的语句
- 结束
但是从输出结果来看,实际情况并不是这样。任务a和任务b还是成功运行了。我的问题是,当main
协程中没有await
点时,这两个任务是怎么有机会运行的?还是说这是asyncio.run
函数造成的某种魔法?
1 个回答
1
“这是不是因为asyncio.run这个函数的魔力呢?”
从我简单浏览这个库的源代码来看,答案很简单,就是“是的”。这个run
函数会创建一个任务,然后调用loop.run_until_complete(task)
,这一步会为这个任务创建一个未来对象(future),接着再调用loop.run_forever()
。正如你观察到的,这个方法至少会执行一次完整的循环。
在这次循环结束时,循环会退出,因为和task
相关的未来对象已经完成了。而你创建的另外两个任务则没有完成。
asyncio的一个主要概念是,任务之间是相互独立的,除非你特别去同步它们。