await执行顺序和事件循环调度
一、await的执行逻辑与顺序规则
await是异步编程中控制协程执行顺序的核心语法,其行为遵循“等待-恢复”规则,具体可分为以下场景:
1. await协程对象:串行穿透执行
当await后跟协程对象(如await write_file(...))时,会直接执行协程函数体,直到遇到下一个await或协程结束。此时:
- 外层协程(如
main)会暂停,等待内层协程(如write_file)完全执行完毕; - 执行顺序严格遵循代码中的
await调用顺序,本质是协程嵌套的串行执行。
示例:
async def main():
await write_file("test.txt", "hello") # 先执行完write_file
await read_file("test.txt") # 再执行read_file
read_file必须等待write_file的所有逻辑(包括内部的异步I/O)完成后才会启动。
2. await异步I/O方法:暂停并让出CPU
当await后跟异步I/O操作(如await f.write(content))时,会触发以下行为:
- 当前协程暂停执行,将控制权交还给事件循环;
- 事件循环监听该I/O操作的完成状态,同时调度其他就绪任务;
- 当I/O操作完成后,事件循环恢复当前协程,从
await处继续执行。
示例:
async def write_file(filename, content):
async with aiofiles.open(filename, "w") as f:
await f.write(content) # 暂停,事件循环可执行其他任务
print("写入完成")
3. awaitTask对象:等待并发任务完成
当await后跟Task对象(如await task1)时,仅等待该Task的最终结果,不阻塞其他Task的执行。此时:
- 多个Task已由事件循环并发调度;
await仅作为“结果获取点”,不影响其他Task的执行顺序。
示例:
async def main():
task1 = asyncio.create_task(write_file("test1.txt", "a"))
task2 = asyncio.create_task(write_file("test2.txt", "b"))
await task1 # 等待task1,但task2已在并发执行
await task2
二、事件循环的调度机制与执行流程
事件循环是异步任务的“调度中枢”,其核心职责是管理任务的生命周期、I/O事件监听与协程切换,具体流程如下:
1. 任务注册阶段
- 根任务注册:
asyncio.run(main())将main协程包装为Task,作为事件循环的入口任务; - 子任务注册:
asyncio.create_task()或asyncio.gather()将协程包装为Task,添加到事件循环的就绪队列。
2. 任务调度阶段
事件循环按以下规则循环执行:
- 执行就绪任务:从就绪队列中取出Task,执行其协程体,直到遇到
await; - 处理暂停任务:
- 若
await的是异步I/O操作:将Task移至I/O等待队列,监听I/O完成事件; - 若
await的是其他协程/Task:将当前Task挂起,等待依赖对象完成;
- 若
- I/O事件回调:当I/O操作完成时,将对应的Task从等待队列移回就绪队列;
- 循环直至结束:重复上述步骤,直到所有Task完成。
3. 调度优先级与并发实现
- 事件循环优先执行就绪队列中的Task,无就绪任务时阻塞等待I/O事件;
- 异步I/O的“等待时间”被充分利用:当一个Task因I/O暂停时,其他Task可立即执行,实现并发。
示例:并发任务的调度时序
async def task1():
await asyncio.sleep(2) # I/O等待2秒
print("task1完成")
async def task2():
await asyncio.sleep(1) # I/O等待1秒
print("task2完成")
async def main():
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
await asyncio.gather(t1, t2)
调度流程:
t1和t2被加入就绪队列,事件循环先执行t1→遇到await sleep(2),t1进入等待队列;- 事件循环执行
t2→遇到await sleep(1),t2进入等待队列; - 1秒后
t2的I/O完成,回到就绪队列并执行完毕,打印“task2完成”; - 再过1秒
t1的I/O完成,回到就绪队列并执行完毕,打印“task1完成”。
三、await与事件循环的协同关系
| 维度 | await的作用 |
事件循环的作用 |
|---|---|---|
| 执行控制 | 暂停当前协程,定义依赖关系 | 调度其他就绪任务,管理协程的暂停/恢复 |
| 任务顺序 | 通过调用顺序决定串行依赖 | 根据I/O状态和就绪队列决定并发执行顺序 |
| 资源利用 | 触发协程暂停,让出CPU | 利用I/O等待时间执行其他任务,提升并发效率 |
核心协同逻辑:
await是协程主动“交出控制权”的信号,事件循环是控制权的“接管者”;- 没有
await,协程会同步执行到底,事件循环无法干预; - 没有事件循环,
await的暂停无法恢复,协程会永久阻塞。
四、关键总结
await的本质:协程的“暂停-恢复”控制器,定义执行依赖,触发CPU出让;- 事件循环的本质:任务调度器+I/O监听器,最大化利用CPU和I/O资源;
- 异步并发的核心:通过
await让出CPU,事件循环调度其他任务,利用I/O等待时间实现并行处理。
简单来说:await决定“什么时候等”,事件循环决定“等的时候做什么”,两者协同实现高效的异步编程。
浙公网安备 33010602011771号