当前位置: 首页 > news >正文

asynccontextmanager

学习MCP开发,经常看到@asynccontextmanager。

@asynccontextmanager 是 Python 3.7+ 标准库 contextlib 提供的一个 装饰器,用来把 异步生成器函数 一键变成 异步上下文管理器(async context manager)

它让你用 async with 管理异步资源的获取/释放时,不必写一整块样板类,只需几行 yield 代码即可。

原理

  • 异步上下文管理器协议 = __aenter__ / __aexit__ 两个协程方法。

  • @asynccontextmanager 帮你自动生成这两个方法;
    你的函数只需负责:
    ① 在 yield 前完成异步初始化(获取资源);
    ② 在 yield 后完成异步清理(释放资源)。

  • yield 值会成为 async with ... as xxx 里的 xxx
    如果 yield 后面还有代码,则无论 with 块是否抛异常,都会执行(等价于 __aexit__)。

举例

import asyncio
from contextlib import asynccontextmanager@asynccontextmanager
async def open_connection(host, port):"""异步建立 TCP 连接,退出时自动关闭。"""reader, writer = await asyncio.open_connection(host, port)print("🔗 连接已建立")try:yield reader, writer          # 把资源交给 with 块finally:writer.close()await writer.wait_closed()print("🔌 连接已关闭")async def main():async with open_connection('httpbin.org', 80) as (rd, wr):wr.write(b'GET /get HTTP/1.0\r\nHost: httpbin.org\r\n\r\n')await wr.drain()data = await rd.read(1024)print('收到:', data[:60])if __name__ == '__main__':asyncio.run(main())

高级技巧

场景正确姿势
异常处理yield 放在 try/finally 里,保证释放逻辑必跑;
如果想区分异常类型,在 except 里重新 raise
传参给清理逻辑把需要释放的字段放在 yield 之前的作用域即可,如上面的 writer
可重入?每次 async with 都会重新执行一次生成器,天然线程/协程隔离。
与同步 @contextmanager 混用不要混用!一个函数只能被一种装饰器装饰。
需要 asyncio 调度器?不需要,只要运行环境是 asyncio.run 或 loop 即可。

手写版本 

async with 会自动调用

__aenter__     __aexit__
class OpenConnection:def __init__(self, host, port):self.host, self.port = host, portasync def __aenter__(self):self.r, self.w = await asyncio.open_connection(self.host, self.port)return self.r, self.wasync def __aexit__(self, exc_t, exc_v, tb):self.w.close()await self.w.wait_closed()

不要把同一个 asynccontextmanager 实例在不同协程里并发

async def maker(): # 被 @asynccontextmanager 装饰的「函数」 ... yield ...cm_func = maker # 这是「工厂函数」,每次调用返回新异步生成器 
cm_obj = cm_func() # 这是「异步生成器对象」,内部保存 ag_running / ag_frame 状态 
acm = _AsyncGeneratorContextManager(cm_obj) # 被装饰后得到的「上下文管理器实例」
  • 关键:acm 内部只持有同一个 cm_obj

  • 如果两个协程 并发 await acm.__aenter__(),就会 同时驱动同一个异步生成器
    导致 ag_running 标志冲突、帧栈错位,最终抛 RuntimeError: anext(): asynchronous generator is already running

翻车示例

import asyncio
from contextlib import asynccontextmanager@asynccontextmanager
async def db():print("[db] connect")yield "connection"print("[db] close")acm = db()                  # ⚠️ 只创建了一次上下文管理器实例!async def task(n):async with acm as conn: # 两个协程并发进入同一个 acmprint(f"[task{n}] got {conn}")await asyncio.sleep(0.1)async def main():await asyncio.gather(task(1), task(2))if __name__ == '__main__':asyncio.run(main())
[db] connect
[db] close
[task1] got connection
RuntimeError: anext(): asynchronous generator is already running
  • 第二条协程试图再 anext 时,生成器还在运行,直接炸。

  • 更严重:第一条协程的 __aexit__ 可能永远不会被调用,资源泄漏

 正确姿势 :每次 async with 都重新调用工厂函数

http://www.dtcms.com/a/578318.html

相关文章:

  • 天津大学邓意达/陈亚楠团队Nano-Micro Lett.研究:热冲击法促新型纳米片自发成长,提升全水解效率
  • 流程架构的解耦与进化设计
  • 企业发展历程网站游戏开发软件免费下载
  • 枣庄市住房和城乡建设局网站如何做网站微信小程序
  • 广州网站排名优化公司20亿做网站
  • Java 获取拼多多商品详情简易版 API 接口实现
  • 邢台市做网站电话wordpress用户名可以修改
  • 成都有几家做网站的公司小型crm系统
  • 工程计算 - Mathcad® 替代方案
  • 广州网站设计建设网站改版 重新收录
  • ESP32C3初应用:点灯及ADC
  • 301 是什么意思?——HTTP 状态码详解与应用
  • 深入解析cursor Token消耗详细分解
  • 深圳网站设计联系电话广告公司海报用的易拉
  • fiddler模拟弱网延时请求
  • 基于单片机的剧本杀场景控制系统(论文+源码)
  • mysql启动提示1067:进程意外终止
  • 网站建设的服务器郑州app制作
  • 智能决策算法的核心原理是什么?
  • springboot基于Java的校园导航微信小程序的设计与实现(代码+数据库+LW)
  • 11.大模型Agent应用
  • 学院网站建设目的与意义手机商城在哪里找到
  • MySQL的GROUP_CONCAT函数详解
  • Temu平台新规全面收紧,卖家如何破局迎战年终大促?
  • 底层视觉及图像增强-项目实践-细节再<十六-9,如何用AI实现LED显示画质增强:总结再回顾>:从LED大屏,到手机小屏,快来挖一挖里面都有什么
  • 怎么做网站后缀识别符号才不会变电脑淘宝网页版
  • 网站访客qq获取系统 报价陕西建设局官方网站
  • 免费做电子书的网站有哪些win2008系统做网站
  • SQL中的函数索引/表达式索引
  • 上海房地产网站建设报价响应式网站开发 三合一建站