asyncio.wait_for 不会自动清理手动创建的资源,仅取消协程执行流;需在协程中用 try/except CancelledError 和 finally 或 async with 显式释放资源。

asyncio.wait_for 会自动清理吗?
asyncio.wait_for 在超时后确实会取消目标 Task 或协程,但它**不会自动处理你手动创建的资源**(比如打开的文件、数据库连接、HTTP 客户端会话等)。它只负责取消协程执行流,不感知你协程内部做了什么。
常见错误现象:
调用 asyncio.wait_for(fetch_data(), timeout=5) 超时后,fetch_data 里用的 aiohttp.ClientSession() 没被关闭,下次再用可能报 RuntimeError: Session is closed 或连接泄漏。
- 超时触发时,
wait_for抛出asyncio.TimeoutError,同时调用task.cancel() - 被取消的协程必须能响应取消 —— 即在
await点检查CancelledError并做清理 - 若协程阻塞在非协作式操作(如
time.sleep()、同步 I/O),cancel()无效,超时后仍会继续运行
如何让协程响应取消并释放资源?
关键是在协程中用 try/except CancelledError 捕获取消信号,并在 finally 或 async with 中释放资源。不要依赖外部“替你善后”。
示例:一个带超时的 HTTP 请求,确保 session 关闭:
立即学习“Python免费学习笔记(深入)”;
import asyncio
import aiohttp
<p>async def fetch_with_timeout(url, timeout=5):
session = aiohttp.ClientSession() # 手动创建
try:
async with session.get(url) as resp:
return await resp.text()
except asyncio.CancelledError:</p><h1>取消信号来了,主动清理</h1><pre class="brush:php;toolbar:false;"> raise # 不吞掉,让上层知道被取消了
finally:
# 无论成功、异常、取消,都关 session
await session.close()正确用法
try: result = await asyncio.wait_for(fetch_with_timeout("https://www.php.cn/link/26bc286a7c996dc103ada0982493576e"), timeout=3) except asyncio.TimeoutError: print("请求超时")
- 用
async with替代手动session.close()更安全,但注意:如果整个async with块被取消,__aexit__仍会被调用(前提是底层实现支持) - 若资源初始化本身耗时或可能失败(如连接池初始化),也要包裹在
try/finally中 - 避免在
finally里做 await-heavy 操作;若必须,加asyncio.shield()防止清理过程又被取消
asyncio.timeout 上下文管理器更可靠吗?
Python 3.11+ 引入了 asyncio.timeout,它比 wait_for 更清晰地分离“超时控制”和“任务调度”,且**推荐用于新项目**。
它本质是上下文管理器,进入时启动计时,退出时自动取消未完成任务 —— 但同样不帮你管资源。
async def fetch_safely():
async with aiohttp.ClientSession() as session:
try:
async with asyncio.timeout(3): # 超时从这里开始计
async with session.get("https://www.php.cn/link/26bc286a7c996dc103ada0982493576e") as resp:
return await resp.text()
except asyncio.TimeoutError:
print("超时了")
raise # 让调用方知道失败原因
<h1>这样写,session 的 async with 保证关闭,timeout 只管时间边界</h1><p>-
asyncio.timeout是可重入的,支持嵌套;wait_for不支持嵌套超时 - 它不包装协程,而是控制代码块执行时间,语义更贴近“这段逻辑不能跑太久”
- 仍需确保所有
await点都能被取消中断 —— 比如aiohttp支持取消,但某些自定义协程若没检查CancelledError,可能卡住
哪些地方最容易漏掉清理?
最常被忽略的不是 HTTP 请求,而是那些“看起来无害”的长期持有资源的操作:
- 手动创建的
asyncio.Queue或asyncio.Event:如果协程被取消时正await queue.get(),队列本身不会自动销毁,但你的消费者可能永远等不到它 - 子进程:
asyncio.create_subprocess_exec启动的进程,超时后需显式proc.terminate()或proc.kill(),否则变成僵尸进程 - 自定义异步迭代器或生成器:若内部有状态(如缓存、连接),
aclose()方法必须被调用,而async for在取消时不一定触发它 —— 建议显式await it.aclose()在finally中 - 第三方库的异步上下文管理器(如
aiomysql.Pool):确认其__aexit__是否真正释放连接,有些池实现会延迟回收
复杂点在于:取消不是“停止执行”,而是“通知你该收尾了”。你得自己决定收尾到哪一层 —— 是关 socket,还是连整个连接池都清空?这取决于业务语义,没有银弹。


















