bigywater.tech
常见报错 · 20

RuntimeWarning: coroutine was never awaited:警告为什么指错位置,if 里漏 await 条件永远为真

“RuntimeWarning: coroutine ‘xxx’ was never awaited” 看起来只是一条警告,背后却常常是整段逻辑根本没有执行、校验永远通过、任务悄悄丢失。这篇在本机把几种忘记 await 的写法逐个复现,看每一种的后果,以及默认警告为什么经常指错位置、怎么让它指对。

0.0046 s
asyncio.sleep(1) 没 await 的耗时:根本没睡
True
bool(协程对象) 的结果:if is_valid(-5): 永远成立,校验形同虚设
0.61 / 0.20 s
async 里 3 个任务用 time.sleep 的总耗时(串行)与用 await asyncio.sleep 的总耗时(并发)

警告指向哪里

实测 · 本机执行输出
  默认(退出码 0):
      r 的类型 = coroutine | r = <coroutine object fetch at 0x10a437a00>
      后面的代码照常往下走
      /opt/homebrew/Cellar/python@3.15/3.15.0_1/Frameworks/Python. … 5/lib/python3.15/asyncio/events.py:94: RuntimeWarning: coroutine 'fetch' was never awaited
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
  python -X tracemalloc=5(退出码 0):
      r 的类型 = coroutine | r = <coroutine object fetch at 0x1061b3a00>
      后面的代码照常往下走
      /opt/homebrew/Cellar/python@3.15/3.15.0_1/Frameworks/Python. … 5/lib/python3.15/asyncio/events.py:94: RuntimeWarning: coroutine 'fetch' was never awaited
      Object allocated at (most recent call last):
        File "<tmp>/s2.py", lineno 6
  python -X dev(退出码 0):
      r 的类型 = coroutine | r = <coroutine object fetch at 0x109aad8d0>
      后面的代码照常往下走
      /opt/homebrew/Cellar/python@3.15/3.15.0_1/Frameworks/Python. … 5/lib/python3.15/asyncio/events.py:94: RuntimeWarning: coroutine 'fetch' was never awaited
      Coroutine created at (most recent call last)
        File "<tmp>/s3.py", line 9, in <module>
        File "<tmp>/s3.py", line 6, in main
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
  python -W error::RuntimeWarning(把这个警告当错误)(退出码 0):
      这行照常执行
      Exception ignored while finalizing coroutine <coroutine object fetch at 0x10a446080>:
              msg, category=RuntimeWarning, stacklevel=2, source=coro
      RuntimeWarning: coroutine 'fetch' was never awaited

协程在别处被销毁时,默认的警告指向 asyncio 内部的行;-X tracemalloc 或 -X dev 才能看到创建它的位置。-W error 只会变成 Exception ignored。

没 await 的后果

实测 · 本机执行输出
  例子(退出码 0):
      asyncio.sleep(1) 没 await:用了 0.0046 s
      await asyncio.sleep(0.3):用了 0.30 s
      save("A") 没 await,0.3 s 之后 done = []
      await save("B") 之后 done = ['B']
      <tmp>/s5.py:7: RuntimeWarning: coroutine 'sleep' was never awaited
        t0 = time.perf_counter(); asyncio.sleep(1); print(f'asyncio.sleep(1) 没 await:用了 {time.perf_counter() - t0:.4f} s')
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
      <tmp>/s5.py:9: RuntimeWarning: coroutine 'save' was never awaited
        save('A'); await asyncio.sleep(0.3); print('save("A") 没 await,0.3 s 之后 done =', done)
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback

函数体不执行,程序继续往下走。asyncio.sleep 不睡、保存逻辑不发生。

if 里漏 await

实测 · 本机执行输出
  例子(退出码 0):
      is_valid(5) 没 await:当作 True,放行
      await is_valid(5):通过
      is_valid(-5) 没 await:当作 True,放行
      await is_valid(-5):不通过
      bool(is_valid(-5)) = True
      <tmp>/s6.py:7: RuntimeWarning: coroutine 'is_valid' was never awaited
        if is_valid(v): print(f'is_valid({v}) 没 await:当作 True,放行')
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
      <tmp>/s6.py:10: RuntimeWarning: coroutine 'is_valid' was never awaited
        print('bool(is_valid(-5)) =', bool(is_valid(-5)))
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback

协程对象永远为真,校验类的判断形同虚设。

没人管的任务

实测 · 本机执行输出
  没跑完的任务(退出码 0):
      main 返回时 log = ['awaited 0.2s']
      asyncio.run 结束之后 log = ['awaited 0.2s'] (0.5 秒的那个被取消了)
  任务抛了异常、没人 await(退出码 0):
      Task exception was never retrieved
      future: <Task finished name='Task-2' coro=<boom() done, defined at <tmp>/s8.py:3> exception=ValueError('boom')>
        File "<tmp>/s8.py", line 3, in boom
          async def boom(): raise ValueError('boom')
      ValueError: boom

asyncio.run 结束时没跑完的任务丢掉;任务里的异常没人取回只会在回收时打印。需要保证做完就保存引用并 await。

async 里的阻塞调用

实测 · 本机执行输出
  耗时对比(退出码 0):
      3 个任务各 time.sleep(0.2):总共 0.61 s(串行)
      3 个任务各 await asyncio.sleep(0.2):总共 0.20 s(并发)
  asyncio.run(main(), debug=True) 里 time.sleep(0.2)(退出码 0):
      Executing <Task finished name='Task-1' coro=<main() done, de … s/Python.framework/Versions/3.15/lib/python3.15/asyncio/runners.py:110> took 0.201 seconds

time.sleep 会阻塞整个循环,并发退化为串行,debug 模式会提示哪一步用了多久。

分步演示

协程未等待
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Python 3.15.0 标准库的 asyncio,每个例子是临时目录里的独立小脚本,用子进程运行并取真实的 stdout / stderr(路径换成 );-X tracemalloc=5、-X dev、-W error::RuntimeWarning、debug=True 都是同一份代码上的不同运行方式
    • 没有验证:mypy / pyright / flake8-async / ruff 等静态检查是否能提前发现这些写法;pytest-asyncio 与 pytest 的 filterwarnings=error 能否让它变成测试失败;TaskGroup 对“没人管的任务”的处理;Python 3.11 及以前的警告文本与行为;fire-and-forget 任务因没有强引用被垃圾回收而中途消失的情形(本篇没有复现);uvloop 下的行为
    京ICP备2026067502号-1京公网安备11010502063487号