未捕获asyncio.CancelledError

时间:2018-08-26 07:58:57

标签: python python-asyncio

为什么在此示例中没有捕获CancelledError

import asyncio

q = asyncio.Queue()

async def getter():
    try:
        v = await q.get()
        print(f"getter got {v}")
    except asyncio.CancelledError:
        print("getter cancelled")

async def test():
    task = asyncio.ensure_future(getter())
    task.cancel()
    await task

def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(test())

if __name__ == '__main__':
    main()

我期望收到“ getter cancelled”消息,但收到了堆栈跟踪:

Traceback (most recent call last):
  File "ce.py", line 22, in 
    main()
  File "ce.py", line 19, in main
    loop.run_until_complete(test())
  File "/usr/lib64/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
concurrent.futures._base.CancelledError

Task.cancel状态:

  

这安排将CancelledError抛出到包装中   协程在整个事件循环的下一个周期。然后协程   有机会清理甚至拒绝使用   尝试/除外/最后。

1 个答案:

答案 0 :(得分:5)

问题在于getter甚至没有执行 start ,您可以通过在开始时添加打印来确认。由于从未输入try块,因此except也没有运行。

之所以发生这种情况,是因为与await相比,ensure_future并没有立即开始执行协程,它只是安排它在下一个事件循环迭代时运行,例如call_soon做普通的功能。由于您立即取消了该任务,因此该任务将从可运行集中删除,并且其协程被关闭,而无需启动。

await asyncio.sleep(0)之前添加task.cancel(),您应该观察到预期的行为。我怀疑您不需要在实际代码中进行此类更改-在极少数情况下任务在运行之前被取消,例如在示例中,它将没有机会获得try / except清除的资源首先。

两个切线说明:

  • 您可能想在处理asyncio.CancelledError之后重新加高,否则它将被抑制。如问题所示,这在getter中不是问题,但是如果代码被埋在函数调用中,则可能是一个问题。甚至更好的是,考虑使用finallywith,它们传播异常并确保不管异常类型如何都释放资源。

  • 当您需要创建任务并运行协程时,loop.create_taskpreferredasyncio.ensure_future。简而言之,尽管两者对协同程序都做相同的事情,但是create_task使得意图更加清晰。 ensure_future的目的是接受更多种类的物体,并获得未指定类型的未来。