区分屏蔽任务取消和当前TA

2024-03-29 13:25:52 发布

您现在位置:Python中文网/ 问答频道 /正文

阅读时: https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel 捕捉cancelederror似乎有两个目的。在

一是可能会阻止任务被取消。在

另一个是确定有什么东西取消了你正在等待的任务。 如何区别?在

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")

Tags: httpsasynciotaskasyncmaindefsleepawait
2条回答

How to tell the difference [between ourselves getting canceled and the task we're awaiting getting canceled]?

Asyncio不容易区分两者。当一个外部任务等待一个内部任务时,它将控制权委托给内部任务的协同进程。结果,取消这两个任务都会将一个^{}注入到完全相同的位置:内部任务中最内部的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完成后设置。此时,我们可以用更具体的ChildCancelled替换CancelledError(我们知道它一定来自内部任务的取消)。另一方面,如果外部任务被取消,它将以常规的CancelledError形式出现在await cont处,并像往常一样进行传播。在

下面是一些测试代码:

^{pr2}$

请注意,在这个实现中,取消外部任务不会自动取消内部任务,但是可以通过显式调用child.cancel()来轻松更改,无论是在parent中,还是在detect_cancel中。在

Asyncio使用与implement^{}相似的方法。在

上下文

首先,让我们考虑更广泛的背景:

caller() > your_coro() > callee()

您可以控制协同程序,但不能控制调用者,只能部分控制被调用者。在

默认情况下,“向上传播”和“向下传播”都是有效的:

(1)
caller1()          +    (2)
                            + > callee()
caller2()  > your_coro()  +
(4)          (3)

在这个图中,语义和非常松散地,如果caller1()被主动取消,那么{}被取消,然后你的协程被取消,然后{}被取消。如果caller2()被主动取消,也会发生大致相同的情况。在

callee()是共享的,因此不是一个普通的协程,而是TaskFuture

你想要什么样的替代行为?在

屏蔽

如果您希望callee()继续,即使caller2()被取消,shield它:

^{pr2}$

反向屏蔽

如果您允许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

保护自己

这个问题值得怀疑-通常你应该允许你的来电者取消你的合作计划。在

一个值得注意的例外是,如果你的调用者是一个框架,并且它是不可配置的。在

^{4}$

相关问题 更多 >