← 中文资源

简体中文版本

Python — RuntimeWarning:协程从未被等待

诊断 RuntimeWarning coroutine never awaited,找到调用 async 函数却不 await 也不调度的位置,并确定性关闭孤儿协程。

阅读英文原文

症状

  警告在最亡羊补牢的时刻——垃圾回收时——才到来:RuntimeWarning: coroutine ‘refresh_cache’ was never awaited,附带 created at 回溯,精确点名构造协程又丢弃它的那一行。业务症状与之配对出现:refresh_cache 本该完成的副作用(缓存刷新、审计写入、通知发送)从未发生。函数从同步改为异步后某个调用点没同步更新、条件分支返回协程对象而不是 await 它,都是同一错误的变体。

诊断思路

  先在 CI 里把警告升级为错误(python -W error::RuntimeWarning),在调用时刻而不是嘈杂的停机时刻定位;再用 grep 找出所有不带 await、不带 create_task、不带 ensure_future 的 async 函数调用行——每一行都是孤儿工厂;最后用 python -X dev 跑应用,开发模式会为每个未等待协程打印带源码行的创建回溯,把停机噪音变成可执行的清单。

根因

  调用 async 函数并不会执行它,只是构造一个协程对象。没有 await、create_task 或 gather,函数体永远不运行,解释器在垃圾回收销毁这个未等待协程时发出 RuntimeWarning——副作用缺失的原因就在于此。

修复与验证

  结果重要就 await;确属 fire-and-forget 就用 asyncio.create_task 调度并保存引用(放进集合、完成时移除——不存引用的任务可能在飞行中被垃圾回收悄悄取消);函数体内没有任何 await 的,改回同步函数反而正确。验证时以 RuntimeWarning 即错误的模式跑受影响流程,断言零异常,并实际观察目标副作用(缓存条目、数据行、通知)可被观测。CI 常态保留 -W error::RuntimeWarning,让未来的孤儿在构建期而不是停机期暴露。

代码示例

RuntimeWarning: coroutine 'refresh_cache' was never awaited
Coroutine created at (most recent call last)
  File "app/api.py", line 42, in handler
    refresh_cache(user_id)      <- called, never run
python -W error::RuntimeWarning -m pytest
async function refresh() {
  const data = await load();
  cache.set(key, data);
}

await refresh();
const pending = new Set();

function fireAndForget(promise) {
  pending.add(promise);
  promise.finally(() => pending.delete(promise));
}

fireAndForget(refresh()); // runs to completion, tracked