在阅读时: https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel 似乎捕获CancelledError有两个目的。
一个有可能阻止您取消任务。
另一个正在确定某件事取消了您正在等待的任务。 如何区分?
async def cancel_me():
try:
await asyncio.sleep(3600)
except asyncio.CancelledError:
raise
finally:
print('cancel_me(): after sleep')
async def main():
task = asyncio.create_task(cancel_me())
await asyncio.sleep(1)
task.cancel()
try:
await task
except asyncio.CancelledError:
# HERE: How do I know if `task` has been cancelled, or I AM being cancelled?
print("main(): cancel_me is cancelled now")
答案 0 :(得分:3)
如何分辨[我们自己被取消和我们正在等待被取消的任务之间的区别]?
Asyncio很难说出区别。当外部任务等待内部任务时,它将控制权委派给内部人员的协程。结果,取消任一个任务都会将CancelledError
注入到完全相同的位置:内部任务中最里面的await
。这就是为什么您无法确定两个任务中的哪个最初被取消了。
但是,可以通过断开await
的链并使用完成回调来连接任务来解决此问题。然后在回调中拦截并检测到内部任务的取消:
class ChildCancelled(asyncio.CancelledError):
pass
async def detect_cancel(task):
cont = asyncio.get_event_loop().create_future()
def on_done(_):
if task.cancelled():
cont.set_exception(ChildCancelled())
elif task.exception() is not None:
cont.set_exception(task.exception())
else:
cont.set_result(task.result())
task.add_done_callback(on_done)
await cont
这在功能上等效于await task
,只是它不直接等待内部task
;它等待一个伪未来,其结果在task
完成后设置。此时,我们可以用更具体的CancelledError
来代替ChildCancelled
(我们知道它必须来自取消内部任务)。另一方面,如果取消了外部任务,则该任务将在CancelledError
处以常规await cont
的形式显示,并照常传播。
以下是一些测试代码:
import asyncio, sys
# async def detect_cancel defined as above
async def cancel_me():
print('cancel_me')
try:
await asyncio.sleep(3600)
finally:
print('cancel_me(): after sleep')
async def parent(task):
await asyncio.sleep(.001)
try:
await detect_cancel(task)
except ChildCancelled:
print("parent(): child is cancelled now")
raise
except asyncio.CancelledError:
print("parent(): I am cancelled")
raise
async def main():
loop = asyncio.get_event_loop()
child_task = loop.create_task(cancel_me())
parent_task = loop.create_task(parent(child_task))
await asyncio.sleep(.1) # give a chance to child to start running
if sys.argv[1] == 'parent':
parent_task.cancel()
else:
child_task.cancel()
await asyncio.sleep(.5)
asyncio.get_event_loop().run_until_complete(main())
请注意,通过此实现,取消外部任务不会自动取消内部任务,但是可以通过在child.cancel()
或{{ 1}}本身。
Asyncio使用类似于implement asyncio.shield()
的方法。
答案 1 :(得分:0)
首先,让我们考虑更广泛的背景:
caller() --> your_coro() --> callee()
您可以控制协程,但不能控制调用者,而只能部分控制被调用者。
默认情况下,取消有效地通过堆栈的上下方向“传播”:
(1)
caller1() ------------------+ (2)
+--> callee()
caller2() --> your_coro() --+
(4) (3)
在此图中,从语义上和非常宽松,如果caller1()
被主动取消,则callee()
被取消,然后协程被取消,然后{{1} }被取消。如果主动取消caller2()
,情况大致相同。
({caller2()
是共享的,因此不是普通的协程,而是callee()
或Task
)
您可能想要什么替代行为?
如果您希望Future
继续执行,即使callee()
被取消,也可以caller2()
:
shield
如果您允许callee_f = asyncio.ensure_future(callee())
async def your_coro():
# I might die, but I won't take callee down with me
await asyncio.shield(callee_f)
死亡,但希望您的协程继续进行,请转换例外:
callee()
这是一个问题-通常,您应允许呼叫者取消协程。
一个值得注意的例外是,如果您的调用方是一个框架,并且它是不可配置的。
async def reverse_shield(awaitable):
try:
return await awaitable
except asyncio.CancelledError:
raise Exception("custom")
async def your_coro():
await reverse_shield(callee_f)
# handle custom exception