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

python async with

就像你走进一家餐厅,服务员对你说“请稍等,我去拿菜单”,然后就去忙别的了。等你点完菜,他又说“请稍等,我去下单”,然后又消失了。这种“短暂离开再回来”的模式,在编程里恰好对应着一种资源管理的办法——async with

很多人一开始看到这个关键字,第一反应是“不就是with加了个async吗?”其实还真不是这么简单。async with的诞生,是因为传统with在处理那些需要等待的操作(比如网络请求、文件读写、数据库连接)时表现得像个急性子——它要求所有事情必须在当下立刻完成。而现实世界里的资源获取和释放,往往需要等待,比如等待数据库连接池给你分配一个连接,或者等待操作系统确认文件已经写入磁盘。

拿生活中的例子来说,你平时用with打开一个普通文件,就像自己去拿一个摆在桌上的笔,伸手就拿得到。但async with处理的是什么呢?就像你打电话订外卖,你说“我要一份套餐”,对方说“好的,等会儿送到”。这时候你不能干等着,可以去刷牙、洗脸、叠被子。async with就是这种“我先提出请求,你去处理,好了通知我”的写法。

从本质上讲,async with是对__aenter____aexit__这两个特殊方法的语法糖封装。任何对象只要实现了这两个方法,就能被async with管理。你可能会想:“这不是和with__enter____exit__很像吗?”确实,区别就在于async with版本需要定义为async def,而且你在里面可以await一些东西。这意味着进入和退出上下文的时候,可以执行异步操作。

它能做什么呢?主要就是管理那些需要“请求-等待-获取”或者“关闭-等待-完成”的资源。最典型的就是网络连接。比如你用aiohttp做HTTP请求,每次拿到一个session,用完后要关闭。如果不用async with,你可能得手动写await session.close(),而且还得小心异常处理,否则连接没关闭就会泄漏。用async with会自动在离开代码块时调用__aexit__,把清理工作安顿好。

另一个常见场景是数据库连接。比如用asyncpg连接PostgreSQL,获取连接、执行查询、归还连接,这一套流程用async with写出来特别自然。你甚至可以嵌套使用,比如先获取一个连接,再在连接上启动一个事务。

关于怎么用,基本的写法是这样的:

asyncwithsome_async_context_manager()asresource:# 使用resource

这里some_async_context_manager()需要返回一个异步上下文管理器对象。比如aiohttp的ClientSession

asyncwithaiohttp.ClientSession()assession:asyncwithsession.get('http://example.com')asresponse:data=awaitresponse.text()

注意这里嵌套了两个async with,外面的管理session的生命周期,里面的管理单个HTTP响应的生命周期。响应对象在离开内层async with后自动释放连接,你不需要手动response.close()

还有一种用法是用@asynccontextmanager装饰器来自定义异步上下文管理器。比如你想创建一个临时锁,进入时获取锁,退出时释放锁:

fromcontextlibimportasynccontextmanager@asynccontextmanagerasyncdeftemp_lock(lock):awaitlock.acquire()try:yieldfinally:lock.release()

然后就可以用async with temp_lock(my_lock):了。这样写比手动acquire/release清晰得多,也更容易避免忘记释放锁。

说到最佳实践,有几点值得特别注意。一是不要滥用async with。有些资源的管理其实不需要异步,比如你对一个本地文件做简单读写,用普通的with就行。如果硬套上async with,反而会引入不必要的协程调度开销,而且代码也变得更难理解。比如你只是读一个几十KB的配置文件,没有必要异步。

二是要注意async with中的异常处理。虽然__aexit__可以接收异常参数,但默认情况下,如果代码块里抛出了异常,它会被传递到__aexit__里,然后__aexit__可以选择吞掉异常、记录日志或者重新抛出。有些库的实现可能不太严谨,异常处理不干净,导致资源没释放。所以最好在async with块内自己捕获特定异常,不要完全依赖上下文管理器来兜底。

三是小心嵌套时的顺序问题。想象一下,你打开两个资源:async with A: async with B:,退出的时候会先退出B再退出A。这个顺序在多数情况下是合理的,但如果你依赖A在B之后存活,就会出现问题。比如你先获取数据库连接池的连接(A),再在这个连接上开始一个事务(B)。如果退出时先退出了事务(B)再归还连接(A),那没问题。但如果顺序反了,可能连接还没还回池里,事务就提前关闭了。好在Python的上下文管理器是栈式的,内层先退出,所以通常没问题。但如果你手动用asyncio.gather或者asyncio.create_task来组合多个上下文管理器,就得格外小心退出顺序。

跟同类技术对比的话,最直接的就是和with对比。with是同步的,async with是异步的。一个简单的判断标准是:如果进入或退出上下文管理器时,有可能发生I/O等待(比如网络、磁盘、等待锁),那就用async with。如果没有等待,用with更简单直接。

另一种对比是跟手动await操作来对比。比如你不用async with,而是写:

session=aiohttp.ClientSession()try:resp=awaitsession.get('http://example.com')data=awaitresp.text()finally:awaitsession.close()

这样写也能达到同样的效果,但代码更冗长,而且不同的库清理资源的API不统一(有的叫close,有的叫release,有的叫disconnect)。async with统一了资源管理的入口,而且保证了即使发生异常也能正常清理。可以说它不仅简化了代码,还提高了健壮性。

还有人会拿async for来跟async with比较。它们其实是两种不同的东西,async for用于迭代异步生成器,比如从流中逐条读取数据。而async with用于管理资源生命周期。虽然都用了async前缀,但语法规则的差别很大。

如果你用过Go语言的defer或者Java的try-with-resources,你会发现Python的async with在异步场景下是挺有特色的。Go的defer更灵活,但需要你自己关心执行顺序,而且没法在defer里直接处理异步操作(除非结合WaitGroup之类的机制)。Java的try-with-resources很简洁,但它是同步的,没有异步版本(虽然Project Loom在探索类似的东西)。Python的这个设计,算是比较自然地把异步和资源管理糅合在了一起,而且对新手也比较友好——async with看起来就像with的孪生兄弟,降低了学习曲线。

最后,还有一个容易被忽视的点:async with不只是用于网络和数据库这类远程资源。它也可以用于异步锁(asyncio.Lock)、信号量(asyncio.Semaphore)等同步原语。比如你想限制并发数量:

sem=asyncio.Semaphore(5)asyncwithsem:# 最多5个协程同时执行这里

这样写比手动acquire/release清晰得多,而且不会因为忘记释放锁而导致死锁。实际上,asyncio.LockSemaphoreCondition等都实现了异步上下文管理器接口,所以可以直接用async with

写代码的时候,把async with当作一个惯例来用就好。只要是你在代码里看到“先打开、后关闭”或者“先请求、后释放”这样的模式,而且涉及异步操作,就可以考虑用async with来表达。它就像是一个代你善后的管家,你只管在“with”块里干你的活,它会默默处理好一切琐事。

http://www.jsqmd.com/news/694947/

相关文章:

  • 星铁速溶茶:如何用自动化脚本彻底解放你的崩坏星穹铁道游戏时间
  • 高通相机HAL层ImageBuffer内存池实战:从Gralloc/CSL申请到MPM线程回收的完整流程
  • 太空开发生存手册:从软件测试视角构建星海可靠基石
  • 03华夏之光永存:电磁弹射+一次性火箭航天入轨方案【第三篇:发射场建设全周期成本精准测算】
  • LumiPixel Canvas Quest 纯净人像创作站:5分钟快速上手,打造你的专属像素艺术
  • 如何在Windows上免费创建虚拟游戏手柄?vJoy完整指南帮你轻松实现
  • python async for
  • 【原创架构续篇】三进制芯片双CMOS基础逻辑单元:引脚定义与状态映射详解
  • 球类运动实测!带赛场数据分析的AI尚运动相机推荐
  • 20天速通LeetCodeday09:关于链表
  • 用C++写个小工具,让希沃管家锁屏在后台“隐身”(附源码与避坑指南)
  • 别再傻傻分不清CWE和CVE了!给开发者的5分钟快速扫盲指南
  • 数据库关系代数操作主要分为核心运算符和扩展运算符两大类
  • 数字永生伦理测试:软件测试从业者的专业视角与框架构建
  • 成年人最贵的错觉:试图在书房里把未来算死
  • 正点原子IMX6ULL开发板LVGL v8.2移植实战:从源码到触屏调试
  • 开发盲盒小程序,这些坑要避开
  • 安道利老师助力临夏腾顺驾校实现AI招生破局
  • MySQL学习笔记:乐观锁VS悲观锁/八股总结
  • SUSE Linux 11实战:用系统自带多路径连接华为OceanStor存储(iSCSI版)
  • VSCode多智能体调试正在淘汰传统单点断点模式!2024年Gartner技术成熟度报告证实:分布式调试已成为AI原生开发刚需
  • 西门子S7-1200 PLC如何通过Modbus TCP读写RFID标签?一个博图V14的实操案例
  • TiDB 混合负载场景下的 ETL 与 CDC 实践
  • 垃圾AI清理技术:系统架构、核心算法与测试挑战
  • WPF资源字典的模块化拼图:MergedDictionaries的实战应用与设计模式
  • 【ESP32实战指南】FreeRTOS核心机制解析:从任务调度到进程间通信
  • AI工程师的黄金十年:选对赛道比努力更重要
  • 4月23日足球赛事分析
  • Pikachu的python一键exp,盲注(base on boolian),盲注(base on time),宽字节注入
  • XOutput:你的老旧游戏手柄重获新生的终极兼容神器