总结asyncio

参考链接

深入理解asyncio(一)

深入理解asyncio(二)

深入理解asyncio(三)

总结

总结一下 py3.7新增的asyncio.run方法你应该尽量用新的,Future是对协程的封装,但是太底层,你应该尽量用其子类Task。await之后Future对象状态才会变成finished,一个对象可以被await是因为它的类实现了__await__方法,为什么await了之后状态就改变了呢,是因为在loop.run_in_executor里面注册了一个_call_set_state回调,task可以通过ensure_future或者create_task轻松的创建。并不是用了async.await你就用对了asyncio的并发了,事实上你应该用gather或者wait将他们攒在一起才能真正并发,另外需要注意不是直接await task就完了,要把task赋值给一个局部变量以后再await才能实现真正并发。

接上一篇,asyncio的gather和wait方法有什么区别呢?gather方法会根据传入的协程的顺序搜集到执行结果,以一个元祖的形式返回执行结果。而wait是返回完成的task集合和挂起的task集合,另外支持通过关键字参数return_when来指定返回的时机,默认是ALLCOMPLETED,还支持FIRSTCOMPLETED和FIRSTEXCEPTION,如果业务上需要这两种状态下返回或者随时取消任务,添加回调就可以调用wait方法。create_task参数要求是协程来创建Task对象,而ensure_future可以接受协程或者Future对象或者awaitable对象,ensure_future源码实际上如果传入协程,直接调用create_task来返回Task对象,如果传入Future对象,直接返回,如果传入awaitable对象,则先await一下这个对象,然后再次调用ensure_future方法,返回Task对象或者Future对象。而wait和gather方法里面都调用了ensure_future。绝大多数场景并发的都是协程,所以用create_task就够了。利用asyncio.shield可以屏蔽取消操作,如果任务取消了,直接调用gather会触发CanceledError异常,如果传入关键字参数return_exceptions=True不会触发异常,而是返回值在取消的那个协程对应的位置是CanceledError异常实例。利用shield保护不被取消要注意顺序必须是先gather协程赋值给一个变量(GatheringFuture对象),然后取消任务,然后再await这个变量。才能确保协程被执行同时返回值是CancleError异常. 另外,py3.7新增了asynccontextmanager可以使用async with调用,异步装饰器内部调用函数可以用yield await func()

接前面两篇,为协程添加成功回调可以使用task.add_done_back。如果要加参数可以设置偏函数来解决。项目里推荐用asyncio.create_task但是在ipython里最好使用loop.create_task因为ipython里await调用的时候ipython注册协程到eventloop所在的线程跟REPL环境不是同一个线程,asyncio.create_task会报错No running event loop。要非在ipython里面使用也不是没有办法,就是自定义一个loop_runner,将loop设置为执行环境里面的,然后用魔法函数%autoawait重设。回调设置还有其他方法call_soon按照设置的顺序执行,call_later安排回调在指定的时间之后执行,call_at在指定的时间执行,不过时间是相对于loop.time。要执行一段同步代码,没有异步库怎么办,可以用loop.run_in_executor让函数在执行器里面执行,默认是线程池,可以设置成进程池,这样就转换成了协程,实现了并发。如果想在其他线程里执行协程,可以用run_coroutine_threadsafe,记得用asyncio.new_event_loop来创建一个新的事件循环,并用别的线程来run_forever,最后使用call_soon_threadsafe回调去停止事件循环。