问题(我想)
contextlib.asynccontextmanager
文档提供以下示例:
@asynccontextmanager
async def get_connection():
conn = await acquire_db_connection()
try:
yield conn
finally:
await release_db_connection(conn)
在我看来,这段代码可能会泄露资源。如果此代码的任务在其await release_db_connection(conn)
行上时被取消,则释放可能会被中断。 asyncio.CancelledError
将从finally
块中的某个位置传播上来,阻止后续清理代码运行。
因此,在实际应用中,如果您正在实现一个带有超时处理的Web服务器,并且在恰当的时间发生超时,可能会导致数据库连接泄漏。
完整可运行示例
import asyncio
from contextlib import asynccontextmanager
async def acquire_db_connection():
await asyncio.sleep(1)
print("Acquired database connection.")
return "<fake connection object>"
async def release_db_connection(conn):
await asyncio.sleep(1)
print("Released database connection.")
@asynccontextmanager
async def get_connection():
conn = await acquire_db_connection()
try:
yield conn
finally:
await release_db_connection(conn)
async def do_stuff_with_connection():
async with get_connection() as conn:
await asyncio.sleep(1)
print("Did stuff with connection.")
async def main():
task = asyncio.create_task(do_stuff_with_connection())
# Cancel the task just as the context manager running
# inside of it is executing its cleanup code.
await asyncio.sleep(2.5)
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
print("Done.")
asyncio.run(main())
Python 3.7.9 的输出结果:
Acquired database connection.
Did stuff with connection.
Done.
请注意,Released database connection
从未被打印。
我的问题
- 这是一个问题,对吗?直觉告诉我,我希望
.cancel()
的意思是“优雅地取消,清理沿途使用的任何资源。”(否则,为什么他们要将取消实现为异常传播呢?)但是我可能错了。也许,例如,.cancel()
应该是快速而不是优雅的。是否有权威来源澄清了在此处.cancel()
应该做什么? - 如果这确实是个问题,我该如何解决?
CancelledError
的事实意味着你仍然可以自己清理 -- 但是你必须在某个可访问代码其他部分的地方缓存类似于conn
状态的等价物。 - Andrew Jaffe