Jupyter中不能用asyncio.run()是因为内核已运行事件循环,而asyncio.run()禁止嵌套;应直接await协程或用nest_asyncio修补,但后者仅适用于调试。

因为 Jupyter 内核已经运行着一个事件循环,而 asyncio.run() 会尝试启动第二个,Python 明确禁止这种嵌套。
为什么 asyncio.run() 在 Jupyter 中直接报错
错误信息通常是:RuntimeError: asyncio.run() cannot be called from a running event loop。这不是代码写错了,而是环境冲突:IPython 内核在启动时就调用了 asyncio.new_event_loop() 并 .run_forever(),整个 Notebook 就跑在这个循环上。而 asyncio.run() 的设计初衷是“从头开始管到底”——它要自己创建、运行、关闭循环,和已有的循环根本没法共存。
常见触发点包括:
- 在单元格里直接写
asyncio.run(my_coro()) - 运行一个外部脚本(比如用
%run script.py),而脚本里有asyncio.run() - 某些封装库(如旧版
httpxCLI 工具或部分爬虫模板)内部硬编码了asyncio.run()
用 nest_asyncio 修补是最省事的方案
它不改你的代码,只改 Python 的 asyncio 行为,让嵌套循环“看起来合法”。安装后只需两行:
立即学习“Python免费学习笔记(深入)”;
!pip install nest_asyncio import nest_asyncio; nest_asyncio.apply()
之后所有原本会报错的 asyncio.run(...) 都能照常执行。它的核心动作是:
- 重写
asyncio.get_event_loop(),让它返回当前运行中的循环而非报错 - 拦截
.run_until_complete()和.close()调用,避免关闭已有循环 - 允许协程在“已有循环中再启一个临时子循环”的语义
注意:nest_asyncio 是调试和探索阶段的利器,但别把它带进生产服务——它绕过了 asyncio 的安全约束,可能掩盖资源泄漏或竞态问题。
更干净的做法:直接 await 协程
Jupyter 单元格本身支持 await(只要内核是 IPython ≥ 7.0)。你不需要 asyncio.run(),直接写:
import asyncio <p>async def fetch_data(): await asyncio.sleep(1) return "done"</p><h1>✅ 正确:直接 await</h1><p>result = await fetch_data() print(result)
这比 asyncio.run() 更轻量,也不触发嵌套检查。但要注意:
- 只能在顶层单元格里用
await,不能在普通函数里写(会报SyntaxError: 'await' outside async function) - 如果协程本身依赖
asyncio.run()初始化的上下文(比如某些全局 event loop 绑定逻辑),await可能行为不一致 - 多个协程并发?用
await asyncio.gather(coro1(), coro2()),不是asyncio.run()套一层
容易被忽略的关键点
真正卡住人的往往不是“怎么让它跑起来”,而是“为什么有时跑一半就卡住、有时又莫名退出”。这是因为:
-
nest_asyncio.apply()必须在第一次调用asyncio.run()之前执行;放在第二块单元格里就晚了 - 重启内核后,
nest_asyncio的修补失效,必须重新apply() - 某些库(如
playwright、httpx.AsyncClient)内部做了 loop 检查,即使加了nest_asyncio,仍可能抛出RuntimeError: Event loop is closed——这时得换用async with或显式传入loop=asyncio.get_event_loop()


















