为什么在这个例子中没有捕获到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
This arranges for a CancelledError to be thrown into the wrapped
coroutine on the next cycle through the event loop. The coroutine then
has a chance to clean up or even deny the request using
try/except/finally.
解决方法:
问题是getter甚至没有开始执行,你可以通过在开头添加一个打印来确认.由于从未输入try块,因此except也没有运行.
发生这种情况是因为,与await相反,ensure_future不会立即开始执行协程,它只是将其调度为在下一个事件循环迭代中运行,就像call_soon对普通函数一样.由于您立即取消了该任务,因此它将从可运行集中删除,并且其协程将在未启动的情况下关闭.
在task.cancel()之前添加await asyncio.sleep(0),您应该观察到您期望的行为.我怀疑你不需要在你的实际代码中做出这样的改变 – 在不太可能的情况下,任务在它运行之前被取消,就像在例子中一样,它将没有机会获得尝试/除了清理的资源在第一位.
两条切线评论:
>您可能希望在处理后重新引发asyncio.CancelledError,否则将被禁止.如问题中所示,这不是getter中的问题,但如果代码隐藏在函数调用中,则可能是一个问题.更好的是,考虑使用finally或with,它传播异常并确保在不考虑异常类型的情况下释放资源.
>当您需要创建任务并运行协程时,loop.create_task为preferred到asyncio.ensure_future.简而言之,虽然两者都对协同程序做同样的事情,但create_task使意图更清晰; ensure_future旨在接受更广泛的对象并获得未指定类型的未来.
标签:python,python-asyncio