为什么在此示例中没有捕获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抛出到包装中 协程在整个事件循环的下一个周期。然后协程 有机会清理甚至拒绝使用 尝试/除外/最后。
答案 0 :(得分:5)
问题在于getter
甚至没有执行 start ,您可以通过在开始时添加打印来确认。由于从未输入try
块,因此except
也没有运行。
之所以发生这种情况,是因为与await
相比,ensure_future
并没有立即开始执行协程,它只是安排它在下一个事件循环迭代时运行,例如call_soon
做普通的功能。由于您立即取消了该任务,因此该任务将从可运行集中删除,并且其协程被关闭,而无需启动。
在await asyncio.sleep(0)
之前添加task.cancel()
,您应该观察到预期的行为。我怀疑您不需要在实际代码中进行此类更改-在极少数情况下任务在运行之前被取消,例如在示例中,它将没有机会获得try / except清除的资源首先。
两个切线说明:
您可能想在处理asyncio.CancelledError
之后重新加高,否则它将被抑制。如问题所示,这在getter
中不是问题,但是如果代码被埋在函数调用中,则可能是一个问题。甚至更好的是,考虑使用finally
或with
,它们传播异常并确保不管异常类型如何都释放资源。
当您需要创建任务并运行协程时,loop.create_task
是preferred至asyncio.ensure_future
。简而言之,尽管两者对协同程序都做相同的事情,但是create_task
使得意图更加清晰。 ensure_future
的目的是接受更多种类的物体,并获得未指定类型的未来。