DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Jupyter 调用 asyncio.run 报 running event loop:在单元格中等待协程

Jupyter 单元格已有事件循环时,使用顶层 await 等待异步函数。区分普通脚本入口、autoawait 配置和库内部同步入口,保留异常与结果传递。

同一段异步代码在终端脚本里能运行,复制到 Jupyter 单元格后却停在 RuntimeError: asyncio.run() cannot be called from a running event loop。如果使用标准 IPykernel,先看调用入口:内核已经管理事件循环,单元格又尝试用 asyncio.run() 启动一条循环。

下面的脚本入口适用于 Python 3.7 及以上;单元格示例要求支持顶层 await 的 IPython 7.0 及以上、IPykernel 5.0 及以上,并使用 asyncio 的 autoawait 集成。其他内核或被定制的执行器要核对自己的调用约定。

单元格直接 await,普通脚本保留 asyncio.run

先把异步业务函数与入口拆开。下面是无网络、无文件写入的示例函数,可以放在单元格中定义:

import asyncio

async def prepare_report():
    await asyncio.sleep(0)
    return {"status": "ready"}

在支持 autoawait 的单元格中,使用:

result = await prepare_report()
print(result)

这里的 await 等待业务完成,后续代码才能使用结果。如果只有 prepare_report() 而没有等待或调度,得到的是协程对象;这会留下另一类coroutine was never awaited 问题。

同一个业务函数放进普通 .py 脚本时,入口可写成:

import asyncio

async def prepare_report():
    await asyncio.sleep(0)
    return {"status": "ready"}

async def main():
    result = await prepare_report()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

这份脚本假定当前线程没有其他运行中的事件循环。if __name__ == "__main__" 只控制入口是否执行,不检查事件循环;把这段代码粘进单元格,并不能靠该条件避开冲突。普通脚本也不能直接放顶层 await,否则会出现 SyntaxError: 'await' outside function。

加了 await 仍报语法错误,检查执行方式

确认代码真正交给了支持 autoawait 的单元格执行,而不是把整份文件交给普通 Python 解释器。编辑器里有单元格分隔符,也不代表终端运行该文件时支持顶层 await。

在 IPython 单元格中,可查看 autoawait 配置;以下是 IPython 魔法命令,不是 .py 文件语法:

%autoawait

如果明确要使用 asyncio,且当前集成被关闭或切换,可以在该交互会话中选择:

%autoawait asyncio

同时核对实际内核使用的 Python、IPython 和 IPykernel 版本。终端 IPython 与 IPykernel 的循环生命周期存在差异,不能仅凭程序运行在“IPython”里,就断定所有代码块都已有运行中的循环。

库函数内部调用 run,需要调整那个入口

有时单元格写的是普通函数调用,报错却来自库内部的 asyncio.run()。给这个同步函数外面加 await,不会阻止它先执行内部的同步入口;需要查看库是否提供可等待的异步接口。

自己维护的库应暴露 async def 业务函数,由脚本入口决定是否用 asyncio.run(),由 notebook 或异步框架直接等待。这样异常与返回值仍沿原来的调用链传递。若库只有同步入口,应按其支持方式使用,或者在独立脚本中运行;不要随意调用未公开的内部协程。

create_task() 适用于明确要调度后台工作的场景,不会同步返回业务结果。如果后续单元格马上依赖结果,仍要保留任务并 await task,不能用 task.result() 代替等待尚未结束的任务。

换成 run_until_complete()、新建循环或 Python 3.11 起的 Runner.run(),都不能绕过同一线程已有运行中循环的限制。也不要关闭内核管理的循环来给业务入口让路。nest_asyncio 会修改循环行为,需要单独评估具体依赖和版本;这里的普通单元格调用无需补丁。

调整后从原来的单元格入口核对:等待完成后能取得返回值,业务异常能到达调用处,且不再有遗留的未等待协程。若业务包含发送请求或写入操作,重跑前先确认上次是否已有部分副作用,不能把入口错误当作所有步骤都没有执行的证明。