【问题标题】:Is suppressing `asyncio.CancelledError` acceptable here?在这里可以接受抑制 `asyncio.CancelledError` 吗?
【发布时间】:2023-07-10 01:31:01
【问题描述】:

例子:

    with suppress(asyncio.CancelledError):
        [await t for t in asyncio.all_tasks(loop=self.loop)
            if t is not asyncio.current_task()]

为避免Task was destroyed but it is pending! 警告,我必须在取消后等待任务,但等待它们会导致终端被CancelledError 发送垃圾邮件。我知道它已取消,但我不需要看到它。

在这里使用contextlib.suppress 是否会对取消产生负面影响?我可以避免看到已取消错误(或任务已销毁警告而无需等待)的唯一其他方法是使用asyncio.wait 而不是asyncio.gather 开始我的初始任务。出于某种原因,wait 似乎抑制了异常。我在wait 上使用return_when=asyncio.FIRST_EXCEPTION,在gather 上使用return_exceptions=True。但似乎无论我如何设置他们的关键字参数,gather 都会打印异常,而 wait 不会。

【问题讨论】:

    标签: python task python-asyncio cancellation graceful-shutdown


    【解决方案1】:

    CancelledError 在 asyncio 中用于两个目的:一个是发出取消信号 request - 这是你在协程中获得的那个被取消 - 另一个是发出取消信号 response - 这是你在等待任务的协程中得到的那个。抑制取消请求是一个坏主意,因为它会使协程无法响应取消,从而导致以后出现问题。但是抑制响应是完全可以的,因为您可能希望等待取消的协程(例如,避免此警告)而不传播异常。

    请注意,您所做的方式看起来不太正确,因为列表理解将在 首先 CancelledError 终止,因此您不会等待其他协程。正确的方法是将suppress 放入循环中,类似于:

    for t in tasks:
        with contextlib.suppress(asyncio.CancelledError):
            await t
    
    # or, simpler:
    await asyncio.gather(*tasks, return_exceptions=True)
    

    wait() 不会传播异常,因为它返回的是 futures 集合而不是它们的结果。如果您尝试访问返回的期货的结果,您会遇到异常。 gather(return_exceptions=True) 返回结果和异常的混合,它不应该引发任何事情。如果是,请编辑问题以提供一个最小示例。

    【讨论】:

    • 这行得通,谢谢。不知道为什么我现在早些时候会从中得到异常,但这可能是其他原因的结果。不过,我现在将使用gather。
    • 顺便说一句:because the list comprehension will terminate on first CancelledError 你确定吗?它似乎完全忽略了错误,与普通的 try/except 不同
    • @Break suppress 将停止传播异常,但列表解析将在遇到异常时立即停止迭代。例如。试试with suppress(ZeroDivisionError): [(print(a), a/0) for a in range(10)],你会看到只打印了第一个数字。