1. 异常处理不完整
# ❌ 危险写法
async with resource:
raise RuntimeError("Oops")
# 如果 resource.__aexit__ 中也抛出异常,原始异常会被隐藏
# ✅ 正确写法
try:
async with resource:
raise RuntimeError("Oops")
except Exception as e:
# 可以正确处理所有异常
pass
2. 嵌套上下文管理器的顺序问题
async with a() as a_obj:
async with b() as b_obj:
# 如果 b.__aenter__ 失败,a.__aexit__ 仍然会被调用
# 但如果 a.__aexit__ 也失败,可能掩盖 b 的错误
pass
3. 忘记 await aexit的清理
class BadAsyncManager:
async def __aenter__(self):
return self
def __aexit__(self, exc_type, exc_val, exc_tb):
# ❌ 缺少 async 关键字!
self.cleanup() # 同步方法,但可能执行异步操作
async def cleanup(self):
await asyncio.sleep(1)
4. 资源泄漏风险
async def risky_function():
manager = AsyncResource()
# ❌ 忘记使用 async with
# ❌ 或者中途 return/break/continue
resource = await manager.__aenter__()
if some_condition:
return # 资源泄漏!
# 应该用 async with 包装
5. 上下文管理器与取消的交互
async with resource:
task = asyncio.create_task(long_running_operation())
await asyncio.sleep(0.1)
task.cancel() # 如果此时正在执行 resource.__aexit__,行为不确定
try:
await task
except asyncio.CancelledError:
# resource.__aexit__ 可能还在执行
pass
6. 最佳实践
# ✅ 使用 asynccontextmanager(Python 3.7+)
from contextlib import asynccontextmanager
@asynccontextmanager
async def safe_resource():
try:
resource = await setup()
yield resource
finally:
await cleanup(resource) # 确保清理被执行
# ✅ 正确处理异常传播
class SafeAsyncManager:
async def __aexit__(self, exc_type, exc_val, exc_tb):
try:
await self.cleanup()
except Exception as cleanup_err:
if exc_type is None:
# 如果没有原始异常,抛出清理异常
raise cleanup_err
# 如果有原始异常,记录清理异常但传播原始异常
logging.error(f"Cleanup failed: {cleanup_err}")
raise # 重新抛出原始异常
7. 测试建议
# 测试异步上下文管理器
async def test_async_context_manager():
# 测试正常情况
async with manager:
pass
# 测试异常情况
with pytest.raises(SomeError):
async with manager:
raise SomeError
# 测试取消
task = asyncio.create_task(use_manager())
await asyncio.sleep(0.1)
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
关键提醒:
总是用 try-finally 包装 异步资源清理
正确处理异常链,避免掩盖重要异常
注意取消传播 到异步清理操作
考虑使用 asynccontextmanager装饰器
充分测试 各种边界情况(正常、异常、取消)
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050070/viewspace-3173206/ ,如需转载,请注明出处,否则将追究法律责任。