asyncio.run()不能在已运行事件循环中调用,因Python禁止同线程嵌套事件循环;应在顶层脚本中使用,而在Jupyter、Django shell等环境改用await或asyncio.run_coroutine_threadsafe()桥接。

asyncio.run() 不能在已运行的事件循环中调用
直接在 Jupyter、Django shell 或某些 Web 框架(如 FastAPI 的依赖函数)里调用 asyncio.run() 会报 RuntimeError: asyncio.run() cannot be called from a running event loop。这不是代码写错了,而是 Python 不允许嵌套启动事件循环。
实操建议:
- 在普通 .py 脚本中,用
asyncio.run(main())是安全且推荐的入口方式 - 在已有事件循环的环境里(比如 IPython),改用
await main(),或显式检查:try: asyncio.run(main()) except RuntimeError: await main() - 避免在同步函数里临时起一个
asyncio.new_event_loop()—— 容易引发线程/循环生命周期混乱
aiohttp.ClientSession() 必须异步创建和关闭
很多人把 aiohttp.ClientSession() 当成类实例直接 session = aiohttp.ClientSession(),结果请求发不出去,或者报 RuntimeError: Session is closed。根本原因是:它不是普通对象,而是一个需要 await 初始化的协程。
常见错误写法:
session = aiohttp.ClientSession() # ❌ 错!这只是个协程对象,没执行 await session.get(url) # ❌ 运行时失败
正确做法(必须用 async with):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
关键点:
立即学习“Python免费学习笔记(深入)”;
-
async with确保底层 TCP 连接池被复用,否则每请求都新建连接,吞吐量断崖下跌 - 不手动
await session.close()——async with已隐式处理,手动关可能触发重复关闭异常 - 不要跨
async with块复用session,它不是线程安全的,也不支持跨协程共享
并发数控制不当会导致 ConnectionResetError 或 429
写 await asyncio.gather(*[fetch(url) for url in urls]) 看似简单,但若 urls 有 1000 个,就等于瞬间发起 1000 个 TCP 连接。实际中常遇到:ConnectionResetError: [Errno 54] Connection reset by peer(服务端主动断连),或返回 429 Too Many Requests(限流)。
解决思路是加并发节流:
- 用
asyncio.Semaphore(10)限制同时最多 10 个请求:sem = asyncio.Semaphore(10) async def fetch_with_limit(url): async with sem: async with aiohttp.ClientSession() as session: async with session.get(url) as resp: return await resp.text() - 别只靠
time.sleep()或asyncio.sleep()降频 —— 它们不阻止并发数,只是延后开始时间 - 对不同域名,可按需分配独立
Semaphore,避免一个慢接口拖垮整批请求
超时设置必须显式传给 ClientSession 和 request
默认情况下,aiohttp 的连接和读取都没有超时,一旦目标服务器卡住或丢包,协程就永远挂起,整个并发队列被堵死。现象是:程序看起来“卡住”,CPU 占用低,但没任何响应或报错。
正确做法是两级超时:
- 在
ClientSession构造时设总超时:timeout = aiohttp.ClientTimeout(total=10, connect=3, sock_read=5) async with aiohttp.ClientSession(timeout=timeout) as session:
- 每个
session.get()可再覆盖(可选):async with session.get(url, timeout=aiohttp.ClientTimeout(total=8)) as resp:
- 注意:
connect控制 DNS + TCP 握手;sock_read控制响应体接收;total是硬上限,优先级最高
漏掉超时是线上 asyncio HTTP 服务最隐蔽的雪崩诱因 —— 表面代码没问题,压测时却大量协程堆积,内存缓慢上涨,最终 OOM。


















