从contextlib asynccontextmanager返回Future是否存在安全风险?
日志检查的上下文管理器实现分析
原方案的坑点
- 没必要用Future瞎折腾:完全不需要通过
asyncio.Future来传递结果,asynccontextmanager本身就能通过yield传递结果载体,额外加个Future纯属多此一举,还容易写错。 - 异常处理容易掉链子:如果
check_log跑的时候抛出异常,原方案要是没捕获并调用fut.set_exception(),这个Future会一直处于“待完成”状态,后面调用fut.result()会直接卡死事件循环;就算你想着去捕获异常,手动维护Future的状态也很容易漏。 - 语义不对路:Future本来是用来处理异步任务结果的,而这里上下文退出时的日志检查是确定要执行的操作,用Future传递结果属于用错了工具。
靠谱的替代写法
直接利用asynccontextmanager的特性,搞个简单的结果容器,在上下文退出时把日志检查结果存进去就行,代码清爽还不容易错:
代码示例
from contextlib import asynccontextmanager import asyncio # 模拟实际的日志检查函数 async def check_log(start_tag, end_tag): # 这里写真正的日志筛选逻辑:根据start和end标记捞对应时间段的日志,检查特定事件 return f"[{start_tag} -> {end_tag}] 日志检查完成:特定事件已触发" class ResultBox: """用来存结果的简单容器""" def __init__(self): self.value = None class Foo: @asynccontextmanager async def bar(self): start_tag = "start" print(start_tag) result_box = ResultBox() try: # 把结果容器yield出去,上下文里的代码也能按需用它 yield result_box finally: end_tag = "end" print(end_tag) # 退出上下文时统一执行日志检查,把结果放进容器 result_box.value = await check_log(start_tag, end_tag) # 实际使用的例子 async def main(): foo = Foo() async with foo.bar() as box: # 这里写你要执行的目标操作 await asyncio.sleep(1) # 模拟业务逻辑执行 # 退出上下文后直接拿结果 print(box.value) asyncio.run(main())
这个写法的好处
- 简单明了:去掉了冗余的Future,逻辑一眼就能看懂,不用绕弯子。
- 异常不翻车:
finally块保证check_log一定会执行(除非事件循环被硬杀),而且check_log的异常会直接抛给调用方,方便你捕获处理,不会出现Future卡死的情况。 - 逻辑对味:上下文管理器的流程完全贴合你的业务需求——先打start标记,执行操作,再打end标记+检查日志,语义顺得很。
内容的提问来源于stack exchange,提问作者CosmicallyCosmo
相关产品推荐
相关产品推荐

