{a1}文档给出了以下示例:
@asynccontextmanager
async def get_connection():
conn = await acquire_db_connection()
try:
yield conn
finally:
await release_db_connection(conn)
在我看来,这可能会泄露资源。如果此代码的任务是cancelled,而此代码位于其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()
在这里应该做什么李>
专注于保护清理工作不被取消是一种转移注意力的做法。有许多事情可能出错,而上下文管理器无法知道
正确处理错误是资源处理实用程序的责任
release_db_connection
不能被取消,它必须保护自己不被取消李>async with
上下文管理器。内部也可能涉及进一步的保护,例如防止取消李>注意:异步清理很棘手。例如,a simple ^{} is not sufficient if the event loop does not wait for shielded tasks. 避免发明自己的保护,依靠底层框架做正确的事情
任务的取消是a)仍然允许异步操作且b)可能被延迟/抑制的正常关机。明确允许准备好处理
CancelledError
以进行清理的协程强制关机是
coroutine.close
/GeneratorExit
。这对应于立即同步关机,并禁止通过await
、async for
或async with
进行暂停您可以使用
asyncio.shield
保护任务为了保证上下文管理器正常关闭,我只在main()
中做了更改:相关问题 更多 >
编程相关推荐