python - 异步上下文管理器是否需要保护它们的清理代码不被取消?

标签 python python-asyncio cancellation resource-leak resource-cleanup

问题(我认为)
contextlib.asynccontextmanager 文档给出了这个例子:

@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() 在这里应该做什么?
  • 如果这确实是个问题,我该如何解决?
  • 最佳答案

    您可以使用 asyncio.shield 保护任务以保证上下文管理器正常关闭,我只在 main() 中进行了更改:

    async def main():
        task = asyncio.create_task(do_stuff_with_connection())
        # shield context manager from cancellation
        sh_task = asyncio.shield(task)
        # Cancel the task just as the context manager running
        # inside of it is executing its cleanup code.
        await asyncio.sleep(2.5)
        sh_task.cancel()  # cancel shielded task
        try:
            await sh_task
        except asyncio.CancelledError:
            pass
    
        await asyncio.sleep(5)  # wait till shielded task is done
    
        print("Done.")
    

    关于python - 异步上下文管理器是否需要保护它们的清理代码不被取消?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68624314/

    相关文章:

    python - 列表 > 列表列表

    python - 从大型文本文件中过滤停用词(使用包 : nltk. 语料库)

    node.js - bramqp 和 node.js : Error on cancelling consumer (basic. 取消)

    cancellation - 如何获取 Agora io 屏幕共享取消事件?

    python - 按层次顺序打印树的内容,使用eval函数读取python中的输入树

    带有 C++ std::map 的 Python dict.update()?

    python - asyncio.Semaphore 运行时错误 : Task got Future attached to a different loop

    python - AIOFiles 比正常文件操作花费更长的时间

    python - 将嵌入式 python asyncio 集成到 boost::asio 事件循环中

    c# - 正确处理任务取消的一般方法