asyncio.CancelledError终于是BaseException了
经常写 Python 异步代码的人可能都知道,当前 Python 版本(3.7.4-)中,,当一个 coro 被取消时,会抛出 CancelledError,而这个 CancelledError 定义为:
1 | # asyncio/exceptions.py |
concurrent.futures.CancelledError 定义在 lib/concurrent/futures/_base.py中:
1 | class Error(Exception): |
也就是说,CancelledError的继承链是
1 | Exception |
问题就出在这个 Exception 身上。平时我们写代码,在不关心异常的情况下经常会有这样的设计:
1 | while True: |
如果运行时,任务在 download() 中就被取消,那么此时抛出的 CancelledError 会被 exception Exception 捕捉到,从而无法正确取消。所以,为了满足设计要求,我们实际上需要把上面的代码修改成:
1 | try: |
这就非常不合理了,每一个涉及到捕捉异常的部分都需要如此处理。而如果按照类似 KeyboardInterrupt 或是 SystemExit的继承链,直接继承 BaseException,显然就不会出现这种问题。
在2018年1月,有人在 BPO 提出了这个问题:BPO32528,在经过几番争吵后,终于这个看上去有点破坏向后兼容的设计失误(就我看来并不会有大的影响)在2019年5月被修改了,并应该会在 Python 3.9.0 跟我们见面(预计2020年6月)🤦