0

0

如何正确包装协程的 __await__ 方法以实现异步资源操作的日志记录

碧海醫心

碧海醫心

发布时间:2026-03-09 09:07:08

|

688人浏览过

|

来源于php中文网

原创

本文详解如何通过自定义 __await__ 方法安全包装异步上下文管理器(如 asyncpg.pool.PoolAcquireContext),确保日志计数器仅在连接真正获取/释放后更新,避免因 await 被取消或异常导致状态不一致。

本文详解如何通过自定义 `__await__` 方法安全包装异步上下文管理器(如 `asyncpg.pool.poolacquirecontext`),确保日志计数器仅在连接真正获取/释放后更新,避免因 `await` 被取消或异常导致状态不一致。

在异步数据库连接池监控场景中,精确跟踪连接的实际获取(acquire)与释放(release)时机至关重要。常见误区是直接在 __await__ 中提前调用 acquire(),但这会导致计数器在协程尚未完成、甚至被取消时就被错误递增——因为 __await__ 仅返回一个可等待对象(generator),并不保证其后续一定会被 await 执行完毕。

正确的做法是:将日志逻辑嵌入协程执行流本身,利用 Python 协程生成器的 yield from 机制,使 acquire() 和 release() 分别在协程开始和结束(包括异常退出)时精准触发。

科大讯飞-AI虚拟主播
科大讯飞-AI虚拟主播

科大讯飞推出的移动互联网智能交互平台,为开发者免费提供:涵盖语音能力增强型SDK,一站式人机智能语音交互解决方案,专业全面的移动应用分析;

下载

以下是关键实现:

class LoggingPoolAcquireContext:
    def __init__(self, pool_acquire_context: asyncpg.pool.PoolAcquireContext, cnt_logger: CntPoolLogger):
        self.pool_acquire_context = pool_acquire_context
        self.cnt_logger = cnt_logger

    def __await__(self):
        # ✅ 正确:acquire 在 await 开始时执行(即连接即将被获取)
        self.cnt_logger.acquire()
        try:
            # ✅ yield from 将控制权交还给底层协程,并透传其执行结果
            # ✅ 协程完成后(无论成功或异常),finally 块确保 release 执行
            return (yield from self.pool_acquire_context.__await__())
        except BaseException:
            # ⚠️ 注意:若 acquire 成功但后续 await 失败(如网络中断),连接可能未被实际使用
            # 此处仍需 release,否则连接泄漏;业务层应结合连接健康检查进一步处理
            self.cnt_logger.release()
            raise
        finally:
            # ✅ 确保 release 总在协程生命周期结束时调用
            # (注意:此处不能无条件 release!见下方说明)
            pass

    # ✅ 更健壮的实现:仅在 __aenter__ 成功后才 release,避免重复释放
    async def __aenter__(self):
        conn = await self.pool_acquire_context.__aenter__()
        self.cnt_logger.acquire()  # 实际获取连接后计数
        return conn

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        try:
            await self.pool_acquire_context.__aexit__(exc_type, exc_val, exc_tb)
        finally:
            self.cnt_logger.release()  # 确保连接归还后计数

⚠️ 重要注意事项

  • __await__ 中的 finally 块不能直接调用 release() —— 因为 __await__ 只负责协程迭代,不等价于资源生命周期管理;它可能被多次 await 或中途丢弃(如被 asyncio.wait_for 取消),导致 release() 被误触发。
  • 推荐优先使用 __aenter__/__aexit__ 组合(即 async with 语义),因其语义明确、生命周期可控,且与 asyncpg 原生行为完全兼容。
  • 若必须支持裸 await(如 conn = await pool.acquire()),则 __await__ 应仅包装 __aenter__ 的调用,并确保 release() 由配套的 __aexit__ 或显式清理逻辑承担,而非 __await__ 的 finally。
  • CntPoolLogger 的 __del__ 不可靠(无法保证及时调用),应改用上下文管理器或显式 close() + asyncio.atexit 清理日志文件。

总结
包装协程的 __await__ 并非简单“返回另一个 __await__”,而是要理解其本质是生成器协议。通过 yield from 委托执行,并配合 try/except/finally 控制副作用时机,才能实现原子性日志记录。但在生产环境中,更推荐基于 async with 的上下文管理方案,兼顾可读性、健壮性与标准兼容性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

384

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2110

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

357

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

259

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

329

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

420

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

472

2023.10.16

vb连接数据库的方法
vb连接数据库的方法

vb连接数据库的方法有使用ADO对象库、使用OLEDB数据提供程序、使用ODBC数据源等。详细介绍:1、使用ADO对象库方法,ADO是一种用于访问数据库的COM组件,可以通过ADO连接数据库并执行SQL语句。可以使用ADODB.Connection对象来建立与数据库的连接,然后使用ADODB.Recordset对象来执行查询和操作数据;2、使用OLEDB数据提供程序方法等等。

231

2023.10.19

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

59

2026.03.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号