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

关于Python asyncio任务执行顺序与异常处理的疑问

问题分析:asyncio中临界睡眠时间下的任务执行顺序

先明确核心现象:当main中的await asyncio.sleep(1.0000000001)时,任务的后续代码在main结束后执行,且异常未被捕获,这是由事件循环的定时器调度精度和任务执行时机共同决定的。

基础机制回顾

  • asyncio.create_task()会将协程包装为Task并加入事件循环的就绪队列,当前任务(如main)遇到await时,事件循环会切换到其他就绪Task执行。
  • asyncio.sleep()会创建一个定时器,到期后将对应的协程重新加入就绪队列,等待事件循环调度。

三种场景的执行顺序拆解

场景1:await asyncio.sleep(1.1)

  1. main启动,打印main started,创建task并加入就绪队列。
  2. main执行await sleep(1.1)挂起,事件循环调度task执行:打印start executing task,然后await sleep(1.0)挂起。
  3. 1.0秒后,task的定时器到期,事件循环唤醒task:打印done waiting in task,抛出异常,task进入**完成(带异常)**状态。
  4. 再过0.1秒,main的定时器到期,main恢复执行:打印done waiting in main,调用task.result()捕获异常,最后打印end of main正常结束。

场景2:await asyncio.sleep(0.1)

  1. main启动,创建task后挂起等待0.1秒,事件循环调度task执行到await sleep(1.0)挂起。
  2. 0.1秒后main恢复执行,此时task仍在等待1.0秒的定时器,处于PENDING状态,调用task.result()抛出Result is not set.。
  3. main结束后,asyncio.run()终止事件循环,task被直接取消,不会执行后续代码。

场景3:await asyncio.sleep(1.0000000001)(核心疑问场景)

这里的关键是两个定时器的时间差极小,超出了系统时钟的调度精度,导致事件循环的执行顺序出现如下情况:

  1. main启动,创建task后挂起等待1.0000000001秒,事件循环调度task执行到await sleep(1.0)挂起。
  2. 理论上task的1.0秒定时器先到期,但由于系统时钟的粒度限制(如操作系统时钟精度为毫秒级),事件循环先检测到main的1.0000000001秒定时器到期,唤醒main继续执行。
  3. main恢复后打印done waiting in main,此时task的定时器虽然已经到期,但事件循环还没来得及调度task恢复执行,task仍处于PENDING状态,因此task.result()抛出Result is not set.。
  4. main打印end of main后执行完毕,asyncio.run()准备关闭事件循环,但此时事件循环发现task已经处于就绪状态(定时器到期),于是调度task执行剩余代码:打印done waiting in task,然后抛出异常。
  5. 由于main已经执行完毕,没有任何代码会捕获task的异常,因此事件循环输出Task exception was never retrieved的错误信息。

总结

这种现象是异步编程中临界时间调度的典型案例,本质是系统时钟精度无法区分极小的时间差,导致事件循环的任务调度顺序与预期出现偏差。如果需要确保任务的异常被捕获,建议使用await task替代task.result(),因为await会等待任务完成,无论其执行时机如何,都能正确捕获异常。

内容的提问来源于stack exchange,提问作者charlielao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:15:57