Python async/await协程执行顺序不符合预期问题咨询
async/await 与 asyncio 调度逻辑解答
核心本质
asyncio 是单线程实现的协作式并发框架,永远不会主动强制中断正在运行的协程,只有协程主动通过await交出执行权时,事件循环才会切换调度其他待运行任务。
这是产生困惑的核心原因:很多人默认异步框架会像操作系统调度线程一样,按时间片抢占式分配CPU给不同任务,但asyncio完全不做这件事,所有任务切换都靠协程主动"让位"。
为什么asyncio.sleep能实现并发切换
asyncio.sleep本身就是专门实现的可等待对象,当你执行await asyncio.sleep(x)时,它会立刻向事件循环发送信号:
我需要等待x秒,这段时间我不需要占用CPU,请先调度其他任务,等时间到了再唤醒我继续执行。
事件循环收到信号后,会把当前协程挂起到等待队列,立刻转去执行其他就绪状态的任务,直到挂起的协程等待条件满足,才会把它重新放回就绪队列等待调度。
第一次运行三个sleep任务时,三个任务依次启动、遇到sleep就立刻让位,事件循环可以在等待期间来回调度,所以总耗时只和最长的sleep时长一致,符合常规对异步并发的预期。
为什么simple_count看起来"先执行完"
_simple_count里的列表推导[i for i in range(n)]是纯CPU密集的同步代码,整个执行过程没有任何await操作,自然不会主动给事件循环让位。
实际执行流程和计时逻辑是这样的:
- 三个任务被
asyncio.gather提交给事件循环后,事件循环按提交顺序先调度simple_count simple_count触发装饰器的开始计时,随即执行await _simple_count(n),进入核心计算逻辑:从0到1亿的循环生成列表全程占满唯一的工作线程,整整跑了4.18秒,这段时间事件循环完全被阻塞,另外两个sleep_short任务连启动执行的机会都没有- 等
simple_count计算完成、返回结果、打印出4.18秒的耗时后,事件循环才终于获得控制权,开始调度第一个sleep_short - 第一个
sleep_short启动、触发自己的开始计时,执行到await asyncio.sleep(0.1)时立刻让位,事件循环随即调度第二个sleep_short,同样启动、计时、遇到sleep让位 - 0.1秒等待时间到,两个sleep任务被依次唤醒执行完后续逻辑,各自打印0.1秒的耗时
你看到的sleep任务耗时只有0.1秒,是因为装饰器的计时是从协程实际开始执行的时间点算的,没有算它们在就绪队列里被堵了4.18秒的等待时间,并不是它们真的和count任务并行跑了。
关键规则总结
- 所有跑在asyncio事件循环线程里的同步阻塞代码、CPU密集代码,只要没有主动通过
await让出控制权,就会堵死整个事件循环,其他所有任务都要等它执行完才能获得调度 - 协程被提交到事件循环不代表它会立刻开始执行,只有事件循环调度到它的时候才会真正启动
- 不要把协程当成"自动并行的任务",它的本质是可以暂停、恢复的函数,暂停的唯一触发点就是
await
内容的提问来源于stack exchange,提问作者thanasissdr
相关产品推荐
相关产品推荐

