Jupyter中不能用asyncio.run(),因其与IPython已运行的事件循环冲突;应直接await协程,或调试时用nest_asyncio补丁。

直接在 Jupyter Notebook 单元格里写 asyncio.run() 会报错,不是代码写错了,而是环境冲突:IPython 内核已经跑着一个事件循环,asyncio.run() 不允许嵌套启动第二个。
为什么 asyncio.run() 在 Jupyter 里一定报错
错误信息固定是:RuntimeError: asyncio.run() cannot be called from a running event loop。这不是语法或逻辑问题,是 Python asyncio 的硬性限制——单线程只允许一个运行中的事件循环。Jupyter 启动时就调用了 loop.run_forever(),整个内核都依赖它。而 asyncio.run() 的行为是“创建 → 运行 → 关闭”,和已有的循环根本无法共存。
- 常见触发点:
asyncio.run(my_coro())、用%run script.py执行含asyncio.run()的脚本、某些旧版封装库(如部分 httpx CLI 工具)内部硬编码了该调用 - 别试图手动
asyncio.new_event_loop()+run_until_complete()—— IPython 的循环已绑定到内核通信,强行替换会导致输出丢失或单元格卡死
最稳妥的做法:直接 await 协程
Jupyter 单元格本身支持顶层 await(IPython ≥ 7.0,默认满足)。你不需要任何额外包,也不用改协程定义,只要把 asyncio.run() 替掉就行:
import asyncio <p>async def fetch_data(): await asyncio.sleep(1) return "done"</p><h1>✅ 正确写法</h1><p>result = await fetch_data() print(result)</p>
- 只能在单元格顶层用
await,不能在普通函数里写(否则报SyntaxError: 'await' outside async function) - 如果协程依赖
asyncio.run()初始化的全局状态(比如某些老版本数据库驱动绑定了 loop),await可能行为异常,这时需查文档确认是否支持“已有 loop”模式 - 多个协程并行?用
await asyncio.gather(coro1(), coro2()),别一个个await
临时调试用 nest_asyncio 补丁
当你必须保留 asyncio.run()(比如复用外部脚本、测试第三方库),nest_asyncio 是唯一靠谱的修补方案:
!pip install nest_asyncio import nest_asyncio; nest_asyncio.apply()
- 它重写了
asyncio.get_event_loop(),让asyncio.run()“以为”自己在干净环境中运行 - 不关闭原有循环,只是拦截
.close()调用,避免破坏内核通信 - 仅限调试和探索阶段——它绕过了 asyncio 的资源安全检查,生产环境禁用
- 装完必须重启内核才能生效,否则补丁不加载
Playwright、httpx 等库的异步适配要点
很多库提供同步/异步双接口,但混用会直接抛错。例如 Playwright 的同步 API 在异步上下文里调用,会提示 “Use async API instead”。必须整体切换:
from playwright.async_api import async_playwright # ❌ 不能用 sync_playwright
<p>async def run_browser():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
page = await browser.new_page()
await page.goto("<a href="https://www.php.cn/link/e4639aefe47ac53c3df3d8f9846b5161">https://www.php.cn/link/e4639aefe47ac53c3df3d8f9846b5161</a>")
content = await page.content()
await browser.close()
return content[:100]</p><p>result = await run_browser() # ✅ 直接 await</p>
- httpx 同理:用
httpx.AsyncClient替代httpx.Client,所有方法加await - 别在同一个协程里混用 sync/async 接口——即使看起来能跑,也可能因 loop 绑定不一致导致超时或静默失败
- 某些库(如旧版 aiomysql)要求显式传入
loop=asyncio.get_running_loop(),漏传会 fallback 到新建 loop,再次触发嵌套错误
真正容易被忽略的是协程生命周期管理:用 asyncio.create_task() 后没 await 或 cancel(),会在单元格执行完后被强制销毁,触发 “Task was destroyed” 警告——这不会中断运行,但可能丢数据或泄漏连接。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











