bigywater.tech
常见报错 · 19

asyncio.run() cannot be called from a running event loop:不装 nest_asyncio 的几种正确写法

“RuntimeError: asyncio.run() cannot be called from a running event loop” 是在 Jupyter 和 async 框架里调用异步库时最常见的报错,网上常见的答案是装 nest_asyncio。这篇在本机把它复现出来,看清楚为什么,以及不装任何补丁时有哪些正确写法,顺带把几个长得像的 asyncio RuntimeError 一起测了。

0.20 s
await、gather(三个并发)、create_task、to_thread 里 asyncio.run 的耗时,都约等于一个 0.2 秒的协程
3 种
async 里嵌套调用的写法(asyncio.run、run_until_complete、被调函数里藏的 asyncio.run),前两个报不同的 RuntimeError 文本
4 个
同一页里复现的相关 RuntimeError:no running event loop、no current event loop、Event loop is closed、bound to a different event loop

为什么报错

实测 · 本机执行输出
  在 async def 里调用 asyncio.run(inner())(退出码 1):
      Traceback (most recent call last):
      RuntimeError: asyncio.run() cannot be called from a running event loop
      <sys>:0: RuntimeWarning: coroutine 'inner' was never awaited
  在 async def 里调用 loop.run_until_complete(inner())(退出码 1):
      Traceback (most recent call last):
      RuntimeError: This event loop is already running
      <sys>:0: RuntimeWarning: coroutine 'inner' was never awaited
  被 async 代码调用的普通函数里调用 asyncio.run()(最常见:库的同步封装)(退出码 1):
      Traceback (most recent call last):
      RuntimeError: asyncio.run() cannot be called from a running event loop
      <sys>:0: RuntimeWarning: coroutine 'inner' was never awaited
  忘了括号:asyncio.run(main)(退出码 1):
      Traceback (most recent call last):
      TypeError: An asyncio.Future, a coroutine or an awaitable is required

asyncio.run 要新建并运行一个事件循环,一个线程里同一时刻只能有一个在跑。async 代码、Jupyter、asyncio REPL 里都已经有。

Jupyter 里怎么办

实测 · 本机执行输出
  python -m asyncio 里依次输入 await inner() 和 asyncio.run(inner())(退出码 0):
      >>> >>> >>> 42
      >>> >>> asyncio REPL 3.15.0 (main, Oct  9 2026, 11:16:41) [Clang 21.0.0 (clang-2100.3.34.2)] on darwin
      Use "await" directly instead of "asyncio.run()".
      Type "help", "copyright", "credits" or "license" for more information.
      >>> import asyncio
      Traceback (most recent call last):
      RuntimeError: asyncio.run() cannot be called from a running event loop
      exiting asyncio REPL...

直接在顶层 await,不用 asyncio.run。

正确写法

实测 · 本机执行输出
  四种写法(退出码 0):
      await inner(1) -> 2,0.20 s
      gather 三个 -> [2, 4, 6],0.20 s(并发,约一个的时间)
      create_task 再 await -> 10,0.20 s
      asyncio.to_thread(sync_wrapper, 7) -> 14,0.20 s(线程里 asyncio.run 能用)
      5 个线程各自 asyncio.run -> [0, 2, 4, 6, 8],0.21 s

能 await 就 await;并发用 gather / create_task;必须调用自带 asyncio.run 的同步封装时放到线程里。

同时支持两种调用的封装

实测 · 本机执行输出
  run_sync 封装(退出码 0):
      同步代码里调用 -> 2
      async 代码里(会阻塞这个循环)调用 -> 11
      没有运行中的循环时 get_running_loop() -> RuntimeError('no running event loop')
  注意:在 async 代码里这样调用,会让当前事件循环被阻塞到那个协程结束(上面第二行),只是不再报错;能 await 就别这样写

先判断有没有运行中的循环再决定;在 async 里这样调用仍会阻塞当前循环。

相关的几个报错

实测 · 本机执行输出
  在事件循环外(退出码 0):
      asyncio.create_task() 在循环外 -> RuntimeError('no running event loop')
      asyncio.get_running_loop() 在循环外 -> RuntimeError('no running event loop')
      asyncio.get_event_loop() 在循环外、没设置过循环 -> RuntimeError("There is no current event loop in thread 'MainThread'.")
      <tmp>/s8.py:5: RuntimeWarning: coroutine 'inner' was never awaited
        except RuntimeError as e: print('asyncio.create_task() 在循环外 ->', repr(e))
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
  循环已关闭(退出码 0):
      关闭后的循环再 run_until_complete -> RuntimeError('Event loop is closed')
      asyncio.run() 结束后,它用的循环 is_closed() = True
      <tmp>/s9.py:6: RuntimeWarning: coroutine 'inner' was never awaited
        except RuntimeError as e: print('关闭后的循环再 run_until_complete ->', repr(e))
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
  一个 Lock 跨两次 asyncio.run(退出码 0):
      第一次 asyncio.run -> got
      第二次 asyncio.run 复用同一个 Lock -> RuntimeError('<asyncio.locks.Lock object at 0x105da6ba0 [locked]> is bound to a different event loop')

循环外创建任务、循环关闭后再用、跨 asyncio.run 复用同步原语,各有各的 RuntimeError 文本。

分步演示

事件循环嵌套
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Python 3.15.0 标准库的 asyncio,每个例子是临时目录里的独立小脚本,用子进程运行并取真实的 stdout / stderr;asyncio REPL 用 python -m asyncio 从标准输入喂入语句
    • 没有验证:真正的 Jupyter / IPython 内核(用 asyncio REPL 代替);nest_asyncio 补丁的效果与副作用;uvloop 下的行为;Python 3.12 及以前版本的报错文本(尤其 get_event_loop 的行为在 3.14 附近有变化);Windows 上的事件循环策略;trio / anyio 的等价写法;FastAPI、aiohttp 等框架里调用同步封装的实际表现
    京ICP备2026067502号-1京公网安备11010502063487号