Python asyncio异步任务执行时序及输出差异咨询
asyncio 任务调度时序核心规则
所有现象都基于asyncio的三个基础运行逻辑,不存在特殊的"魔法"调度规则:
- 协作式调度特性:协程只有在遇到
await表达式时才会让出事件循环控制权,一旦协程被调度执行,会持续运行到下一个await点、协程执行结束、或者抛出异常才会切走。time.sleep()这类同步阻塞调用不会让出控制权,会直接卡住整个事件循环线程,阻塞期间所有任务调度、定时器检查、IO处理全部暂停。 - 事件循环退出逻辑:
asyncio.run()不会在主协程(传入的main)执行完的瞬间强制终止。它会先把当前已经进入就绪队列等待调度的任务全部执行完毕,之后才会取消所有仍在等待定时器/IO的未完成(pending)任务,最终关闭事件循环。注意:只有已经被事件循环从定时器/IO等待队列移到就绪队列的任务才会被执行完,还在等待队列里的任务会被直接取消,不会等待它们到期。 - 定时器精度说明:
asyncio.sleep(delay)的参数是最小等待时长,不是精确等待时长。实际等待时间受操作系统时钟精度、系统负载、调度开销影响,Windows平台默认时钟分辨率约10-15ms,小于这个差值的sleep参数实际表现可能没有区别。你提到的15ms调度粒度本质就是操作系统的时钟精度限制,不是asyncio本身的特殊设计。
不同sleep时长场景的输出解释
所有场景中foo任务的固定时间线:任务创建后立刻执行第一次print(text),随后挂起等待1秒,若能被调度会执行第二次print(text)。
- 场景1:
main中await asyncio.sleep(1)
时间线:- 启动后
main先打印tim,创建foo任务后让出控制权,foo被调度执行第一次print(text),随后挂起到1秒的定时器队列,main也挂起到同一个1秒的定时器队列。 - 1秒后定时器到期,事件循环将
foo和main全部移到就绪队列,优先调度恢复main执行。 main恢复后直接执行print("finished"),运行结束,没有await点所以不会中途切走。- 主协程结束后,事件循环发现就绪队列里还有等待调度的
foo,于是调度foo执行第二次print(text),之后就绪队列清空,退出循环。
最终输出最后一行text。
- 启动后
- 场景2:
main中await asyncio.sleep(0.5)
时间线:- 前半段和场景1一致,区别是
main的定时器0.5秒就到期。 - 0.5秒时事件循环将
main移到就绪队列调度执行,此时foo的1秒定时器还没到期,仍在等待队列中,没有进入就绪队列。 main恢复后执行print("finished"),运行结束。- 此时就绪队列为空,事件循环直接取消所有等待队列中的pending任务(包括还在等1秒定时器的
foo),foo的第二次print不会执行。
最终没有最后一行text。
- 前半段和场景1一致,区别是
- 场景3:
main中await asyncio.sleep(0.99)
0.99秒和1秒的差值仅10ms,小于常规操作系统的调度精度误差,事件循环实际唤醒时时间已经超过1秒,处理定时器时foo的1秒定时器已经到期,和main一同被移到就绪队列,后续流程和场景1完全一致,因此会输出最后一行text。如果在开了高精度时钟的低负载Linux环境下测试,0.99秒的sleep可能真的在0.99秒就唤醒,此时foo还没到点,就不会输出最后一行text。
加入time.sleep阻塞的场景解释
核心逻辑和之前一致,唯一需要注意的是:同步阻塞的time.sleep执行期间,事件循环完全停转,不会检查定时器是否到期,不会把等待中的任务移到就绪队列。
- 阻塞场景1:
main先sleep 0.5秒,再time.sleep(1)
时间线:- 0.5秒时
main被唤醒调度,此时foo还有0.5秒才到唤醒时间,仍在定时器等待队列。 main开始执行time.sleep(1),事件循环线程被完全卡死1秒,这段时间即使实际时间已经走过1秒,事件循环也不会处理定时器,foo始终留在等待队列,不会进入就绪队列。- 阻塞结束后
main继续执行print("finished"),运行结束。 - 此时就绪队列为空,
foo还在等待队列中属于pending状态,直接被取消,不会执行第二次print,因此没有最后一行text。
- 0.5秒时
- 阻塞场景2:
main先sleep 1秒,再time.sleep(1)
时间线:- 前1秒事件循环正常运行,1秒时定时器到期,事件循环把
foo和main都移到就绪队列,随后调度main执行。 main开始执行time.sleep(1),事件循环再次被卡死1秒,但此时foo已经在就绪队列里了,不是在等待队列中。- 阻塞结束后
main执行print("finished"),运行结束。 - 事件循环发现就绪队列里还有
foo,调度执行第二次print(text),之后退出循环,因此会输出最后一行text。
- 前1秒事件循环正常运行,1秒时定时器到期,事件循环把
内容的提问来源于stack exchange,提问作者JING
相关产品推荐
相关产品推荐

