Python 调用异步函数没有执行,提示 coroutine was never awaited
直接调用 async def 函数只创建协程对象。按当前入口选择 asyncio.run 或 await,需要任务时保留引用并等待结果,避免用忽略警告代替修复。
异步函数里写了操作,调用之后却没有发生,程序还提示 RuntimeWarning: coroutine ... was never awaited:
import asyncio
async def prepare():
await asyncio.sleep(0)
return "ready"
prepare()
调用 async def 定义的函数会创建协程对象,单独这一行不会安排它执行。警告提示的是协程没有被等待或交给任务执行,不是函数已经执行完但没有返回值。本文针对 Python 3.7 及以上的标准 asyncio;第三方框架的入口还需遵守其事件循环约定。
普通脚本从 asyncio.run 进入
如果是独立脚本,当前线程没有运行中的事件循环,可以在程序入口运行顶层协程:
import asyncio
async def prepare():
await asyncio.sleep(0)
return "ready"
async def main():
result = await prepare()
print(result)
if __name__ == "__main__":
asyncio.run(main())
这段示例预期打印 ready。asyncio.run 管理入口所需的事件循环,在协程完成后清理并关闭它,通常应作为整个程序的入口使用一次。
传给入口的是 main() 创建的协程对象,而不是函数名 main。函数内部等待子协程时使用 await prepare();只创建对象、保存到变量里,也不会自动启动它。
已经处于异步代码中,直接 await
如果调用发生在异步处理函数里,沿用已有事件循环:
async def handle_request():
result = await prepare()
return result
这个片段沿用前面的 prepare,由现有框架负责调用 handle_request。同一线程已经运行事件循环时,再调用 asyncio.run 会报错。不能在每个异步函数外面都包一层 run。
若加入 await 后出现“只能在异步函数内使用”的语法问题,应向上检查调用链和入口。单纯把函数声明改成 async def 会改变它的调用方式,上层也必须等待或调度它,不能只改声明就结束。
有些交互环境支持顶层 await,有些普通脚本不支持。判断依据是当前执行环境如何管理协程,而不是代码文件中有没有导入 asyncio。
确实需要并发时,保留任务并等待完成
已有运行中的事件循环,且希望让协程作为任务调度时,可以创建任务,再明确等待它:
async def main():
task = asyncio.create_task(prepare())
# 这里可以安排其他独立的异步工作
result = await task
print(result)
这段 main 可替换独立脚本中的同名函数。任务对象保存在变量里,结果与异常通过 await task 交回调用方。若只是马上等待一个操作,直接 await prepare() 更简单。
不要只创建后台任务就退出入口,也不要丢掉所有任务引用。事件循环只保留任务的弱引用;应用需要管理任务存活、完成和异常。还需检查最终结果,才能确认业务操作成功。
修复后核对警告中指向的具体协程:顶层调用修好后,内部遗漏的异步调用仍可能触发同类警告。确认所有需要完成的操作都在入口返回之前完成,失败能传递给调用方;不要通过过滤 RuntimeWarning 来当作修复。
涉及发送、写入等操作时,先确认此前是否有部分步骤已执行,再决定是否重试,避免重复副作用。回退异步调用链时保留入口和调用约定,不要混用旧的同步调用方式与新的协程返回值。


