【问题标题】:Cancelling an async context manager取消异步上下文管理器
【发布时间】:2019-04-06 16:37:51
【问题描述】:

获取取消任务的句柄很容易:

task = loop.create_task(coro_fn())

# later
task.cancel()

是否可以对异步上下文管理器执行相同的操作?

async with foo() as bar:
   # is it possible to cancel before getting here,
   # while waiting to enter the context manager?
   await bar.baz()

# later
# How do I get a handle to the context manager for cancellation?

有没有办法做到这一点?还是上下文管理器代码需要在自己的任务中运行?

【问题讨论】:

  • 你到底想取消什么?
  • 在上下文管理器的__aenter__ 中执行暂停(可能正在等待获取资源)。我想取消它,以便 asyncio.CancelledError 从中得到提升。
  • 您取消在__aenter__ 中挂起的任务,就像在 asyncio 中的其他任何事情一样——通过确保它在任务中并在其上调用cancel()。详情见我的回答。

标签: python python-asyncio


【解决方案1】:

如何获得上下文管理器的句柄以进行取消?

你不知道,至少不是直接的 - 上下文管理器只是获取和释放资源的方法的方便封装。您的问题中的async with foo() as bar: ... 大致类似于:

_cm = foo()
bar = await _cm.__aenter__()
try:
    await bar.baz()
finally:
    await _cm.__aexit__(*sys.exc_info())

__aenter____aexit__ 都是普通的等待对象,它们的执行 可以被取消,就像await bar.baz()async with 体内的执行一样。

因此,如果“取消上下文管理器”是指取消当前卡在async with 块中的操作,则可以像任何其他取消一样执行此操作。例如,您可以将async with 提取到一个在其自己的任务中运行的协程中,然后将cancel() 提取到该协程中。请注意,无论如何,您都需要获得一个任务才能到达“稍后”部分,而不会在此过程中关闭异步上下文管理器。

这是一个例子:

async def run_foo():
    async with foo() as bar:
       # is it possible to cancel before getting here,
       # while waiting to enter the context manager?
       await bar.baz()

task = asyncio.create_task(run_foo())
# "task" is the handle you can await or cancel at your leisure

【讨论】:

  • 谢谢 - 所以看来我需要手动使用 __aenter__ 来做我想做的事。
  • @Evan 这是合法的(异步上下文管理器协议是公开的,您可以自己调用__aenter__ 之类的方法),但不寻常且可能容易出错,因为您必须记住调用__aexit__你自己,并正确地做。你真的只需要取消进入上下文管理器而没有其他 async with 吗?即使是这样,当async with 正文开始执行时,难道不能使用设置的标志来检查吗?
  • 从概念上讲,上下文管理器正在等待一组项目并产生第一个可用的项目(特别是从连接池中检查连接)。我想取消,因为我想等待一组不同的项目(例如,我想将一个项目添加到正在等待的集合中)。如果不取消,上下文管理器可能会等待无限长的时间,然后我才有机会添加新项目。
  • @Evan 我明白了,这是有道理的。使用适当的超时逻辑包装上下文管理器的抽象可能有助于避免在使用位置出现错误,但我不会将它包含在答案中,因为我不知道用什么替换它 i>,该部分特定于您的应用程序。
猜你喜欢
  • 2016-09-22
  • 2021-10-07
  • 1970-01-01
  • 1970-01-01
  • 2014-07-10
  • 1970-01-01
  • 2022-11-10
  • 2022-07-28
  • 2021-05-26
相关资源
最近更新 更多