asyncio.run() 只能调用一次,因其每次创建并关闭新事件循环,重复调用会报 runtimeerror;它专为脚本入口设计,非循环复用接口。

asyncio.run() 为什么只能调用一次?
因为 asyncio.run() 内部会创建新事件循环、运行完就关闭它,再调用就会触发 RuntimeError: asyncio.run() cannot be called from a running event loop。这不是 bug,是设计约束——它专为“脚本入口”场景服务,不是循环复用接口。
- 想多次执行异步逻辑?直接用
asyncio.get_event_loop().run_until_complete()(Python 3.7+ 推荐改用asyncio.run()的替代方案:手动管理循环) - 在已运行的协程里启动新任务?用
asyncio.create_task()或loop.create_task(),别碰run_until_complete - Jupyter 或 REPL 里调试时容易误调两次
asyncio.run(),报错后得重启 kernel 才能恢复
事件循环怎么实际调度 awaitable 对象?
不是“一遇到 await 就切走”,而是 await 表达式返回一个 awaitable 对象(比如 coroutine、Future、实现了 __await__ 的对象),事件循环把它注册进内部的 ready 队列或等待队列,然后根据状态(如 I/O 完成、超时、被 cancel)决定何时恢复执行。
-
await asyncio.sleep(0)强制让出控制权,相当于“插入一个立即就绪的任务”,常用于避免长协程饿死其他任务 - 纯 CPU 密集型操作(比如大数组计算)不会自动让出,必须手动加
await asyncio.sleep(0)或拆成小块 +asyncio.to_thread()(3.9+) - 底层依赖系统级通知机制:Linux 用
epoll,macOS 用kqueue,Windows 用IOCP;这些不暴露给用户,但影响并发上限和延迟敏感度
自定义事件循环(比如 uvloop)要替换哪些地方?
替换的是事件循环策略(asyncio.AbstractEventLoopPolicy)的默认实现,不是直接换掉 asyncio.run() 本身。uvloop 提供的是更快的 loop 实例,但所有高层 API(create_task、gather 等)行为不变。
- 生效方式只有两种:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())(全局策略),或uvloop.new_event_loop()+asyncio.set_event_loop()(手动设当前线程 loop) - 不能在
asyncio.run()已启动后替换,否则报RuntimeError: Cannot run the event loop while another loop is running - uvloop 不支持 Windows,且部分调试工具(如
asyncio.debug模式)可能失效;生产环境提速明显,开发期建议保留默认 loop 方便排查
loop.run_forever() 和 loop.run_until_complete() 的关键区别
前者是“永动机”,除非你手动调 loop.stop(),否则不会退出;后者是“跑完指定协程就停”,适合单次任务。两者都要求 loop 处于未运行状态,重复调用会报 RuntimeError: This event loop is already running。
立即学习“Python免费学习笔记(深入)”;
-
run_until_complete()是asyncio.run()底层实际调用的方法,它会在结束前自动close()loop —— 所以你不能再对这个 loop 做任何事 -
run_forever()常用于服务器主循环(比如asyncio.start_server()启动后),需要自己管理 shutdown:监听信号、取消 pending task、调loop.stop()再loop.close() - 忘记调
loop.close()会导致资源泄漏(比如 socket fd 未释放),尤其在短生命周期脚本中容易被忽略










