You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Async函数执行顺序判定逻辑及代码执行疑问解析

解析你的asyncio代码执行顺序问题

先看你运行的代码:

from asyncio import create_task, sleep, run

async def print_(delay, x):
    print(f'start: {x}')
    await sleep(delay)
    print(f'end: {x}')

async def main():
    slow_task = create_task(print_(2, 'slow task'))
    fast_task = create_task(print_(1, 'fast task'))

    # 此处执行顺序不符合预期:
    print(0)
    await slow_task
    print(1)
    await fast_task

run(main())

得到的输出:

0
start: slow task
start: fast task
end: fast task
end: slow task
1

具体发生了什么?

我们一步步拆解执行流程:

  1. 调用run(main())启动事件循环,开始执行main协程。
  2. create_task会把传入的协程包装成任务,立即加入事件循环的待执行队列,但此时不会立刻执行——因为当前main协程还在同步执行阶段,没有让出控制权。
  3. 执行print(0),输出0。
  4. 遇到await slow_task:此时main协程被挂起,控制权交还给事件循环,事件循环开始处理队列里的任务:
    • 先调度slow_task对应的print_(2, 'slow task'):输出start: slow task,然后遇到await sleep(2),这个协程被挂起(等待2秒),事件循环继续找下一个可执行任务。
    • 接着调度fast_task对应的print_(1, 'fast task'):输出start: fast task,遇到await sleep(1),协程被挂起(等待1秒)。
  5. 事件循环进入等待状态,直到有任务的等待条件满足:
    • 1秒后,fast_task的sleep(1)完成,事件循环唤醒它,输出end: fast task,fast_task执行完毕。
    • 再过1秒,slow_task的sleep(2)完成,事件循环唤醒它,输出end: slow task,slow_task执行完毕。
  6. 此时await slow_task的等待完成,事件循环恢复main协程的执行,执行print(1),输出1。
  7. 最后遇到await fast_task,但fast_task已经完成,直接跳过,main协程结束,事件循环终止。

如何预测执行顺序?

你困惑的print(1)为什么在所有任务完成后才执行,核心原因是:
await slow_task会一直等待slow_task完全执行完毕,才会继续执行main协程里的后续代码。而在等待slow_task的过程中,事件循环不会闲着——它会把队列里其他已就绪的任务(比如fast_task)全部执行完,直到slow_task完成。这就导致print(1)要等到两个任务都结束后才会运行。

你原本预期print(1)在任务完成前执行,是对await的作用理解有偏差:await不是“触发任务执行后立刻继续当前代码”,而是“暂停当前协程,等待目标任务完成,期间事件循环会处理其他任务”。

核心规则总结

  • create_task仅将协程加入事件循环队列,不会立即执行,必须等当前协程让出控制权(比如遇到await)才会调度。
  • await会挂起当前协程,事件循环会优先处理所有已就绪的任务,直到被await的任务完成,才会恢复当前协程。
  • asyncio采用协作式调度:任务不会被抢占,只有当任务主动让出控制权(比如await操作),事件循环才会切换到其他任务。

这是标准行为吗?会变化吗?

这是asyncio的标准行为,不会随意变化。
不同的事件循环实现(比如默认的SelectorEventLoop或第三方的uvloop)在调度的细节上可能有微小差异,但核心逻辑完全一致:协作式调度,等待任务期间会处理其他就绪任务。只有当asyncio的核心调度规则发生变更时,行为才会变化,但这种情况极少发生。


事件循环与代码的协同机制

asyncio的事件循环是单线程下协程调度的核心,工作流程如下:

  1. 启动:调用run()时,会创建一个事件循环,并将传入的协程作为“主协程”加入调度队列。
  2. 执行协程:事件循环取出队列中的协程,开始执行,直到协程遇到await或结束。
  3. 挂起与调度:当协程遇到await时,会将自身挂起,把控制权交还给事件循环。事件循环会检查所有已注册的可等待对象(任务、sleep、future等),找出那些已经就绪的(比如sleep时间到、任务完成),然后调度它们执行。
  4. 恢复执行:当被await的对象完成时,事件循环会将对应的协程重新加入可执行队列,等待下一次调度。
  5. 终止:当主协程执行完毕,事件循环会清理所有剩余任务,然后终止。

简单来说,事件循环就像一个“任务调度器”,所有协程的执行、挂起、恢复都由它控制,而协程通过await主动让出控制权,实现多任务的协作式并发。

内容的提问来源于stack exchange,提问作者Petko.Maria.John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 02:03:11