asyncio 的 coroutine对象 与 Future对象使用指南
coroutine与Future的关系
看起来两者是一样的,因为都可以用以下的语法来异步获取结果,
result=awaitfuture result=awaitcoroutine
实际上,coroutine是生成器函数,它既可以从外部接受参数,也可以产生结果。使用coroutine的好处是,我们可以暂停一个函数,然后稍后恢复执行。比如在涉及到网路操作的情况下,能够停下函数直到响应到来。在停下的这段时间内,我们可以切换到其他任务继续执行。
而Future更像是Javascript中的Promise对象。它是一个占位符,其值会在将来被计算出来。在上述的例子中,当我们在等待网络IO函数完成时,函数会给我们一个容器,Promise会在完成时填充该容器。填充完毕后,我们可以用回调函数来获取实际结果。
Task对象是Future的子类,它将coroutine和Future联系在一起,将coroutine封装成一个Future对象。
一般会看到两种任务启动方法,
tasks=asyncio.gather( asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ) loop.run_until_complete(tasks)
和
tasks=[ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ] loop.run_until_complete(asyncio.wait(tasks))
ensure_future可以将coroutine封装成Task。asyncio.gather将一些Future和coroutine封装成一个Future。
asyncio.wait则本身就是coroutine。
run_until_complete既可以接收Future对象,也可以是coroutine对象,
BaseEventLoop.run_until_complete(future) RununtiltheFutureisdone. Iftheargumentisacoroutineobject,itiswrappedbyensure_future(). ReturntheFuture'sresult,orraiseitsexception.
Task任务的正确退出方式
在asyncio的任务循环中,如果使用CTRL-C退出的话,即使捕获了异常,EventLoop中的任务会报错,出现如下的错误,
Taskwasdestroyedbutitispending!
task:<Taskpendingcoro=<kill_me()done,definedattest.py:5>wait_for=<Futurependingcb=[Task._wakeup()]>>
根据官方文档,Task对象只有在以下几种情况,会认为是退出,
aresult/exceptionareavailable,orthatthefuturewascancelled
Task对象的cancel和其父类Future略有不同。当调用Task.cancel()后,对应coroutine会在事件循环的下一轮中抛出CancelledError异常。使用Future.cancelled()并不能立即返回True(用来表示任务结束),只有在上述异常被处理任务结束后才算是cancelled。
故结束任务可以用
fortaskinasyncio.Task.all_tasks(): task.cancel()
这种方法将所有任务找出并cancel。
但CTRL-C也会将事件循环停止,所以有必要重启事件循环,
try: loop.run_until_complete(tasks) exceptKeyboardInterruptase: fortaskinasyncio.Task.all_tasks(): task.cancel() loop.run_forever()#restartloop finally: loop.close()
在每个Task中捕获异常是必要的,如果不确定,可以使用
asyncio.gather(...,return_exceptions=True)
将异常转换为正常的结果返回。