Reputation: 345
It's easy enough to get a handle to a task for cancellation:
task = loop.create_task(coro_fn())
# later
task.cancel()
Is it possible to do the same for async context managers?
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?
Is there any way to do this? Or does the context manager code need to run in its own task?
Upvotes: 2
Views: 1593
Reputation: 155356
How do I get a handle to the context manager for cancellation?
You don't, at least not directly - a context manager is just a convenient packaging for methods to acquire and release a resource. The async with foo() as bar: ...
from your question desugars to something roughly like:
_cm = foo()
bar = await _cm.__aenter__()
try:
await bar.baz()
finally:
await _cm.__aexit__(*sys.exc_info())
Both __aenter__
and __aexit__
are normal awaitables, whose execution can be cancelled, just like the execution of await bar.baz()
inside the async with
body.
So if by "canceling a context manager" you mean cancel the operation that the async with
block is currently stuck in, you can do it the same as for any other cancellation. For example, you could extract the async with
into a coroutine that runs in its own task and cancel()
that. Note that you'd need to obtain a task anyway to even reach the "later" section without the async context manager getting closed in the process.
Here is an example:
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
Upvotes: 4