为何在asyncio.gather中使用asyncio.ensure_future?直接传协程可行吗?
先给结论:在你给出的场景里,额外调用asyncio.ensure_future没有明显优势,直接传协程的写法更简洁,完全够用。
下面具体拆解两者的区别和ensure_future的适用场景:
- asyncio.gather本身就会自动把传入的协程对象包装成Task(等同于调用ensure_future或create_task),所以第一种写法里,gather内部已经帮你完成了协程转Task的操作,最终的并发执行逻辑和第二种写法完全一致。
那什么时候需要提前用ensure_future呢?只有当你需要在调用gather之前,对任务做额外操作的时候:
提前启动任务:如果生成任务列表后,你还有其他耗时的异步/同步操作要做,提前把协程转成Task的话,这些任务会立刻进入事件循环开始执行,不用等你调用await gather。比如:
async def get_resources(): async with aiohttp.ClientSession(base_url=BASE_URL) as session: # 提前创建Task,任务立刻开始执行 tasks = [asyncio.ensure_future(fetch(session, res)) for res in resources] # 这里做一些其他操作,比如读取本地文件、计算参数等 await do_something_else() # 等所有任务完成 return await asyncio.gather(*tasks)这种情况下,任务在
do_something_else执行的时候已经在并发跑了,能节省整体耗时。单独操作任务:如果需要跟踪单个任务的状态(比如判断是否完成
task.done())、给任务添加回调函数(task.add_done_callback(...)),或者手动取消任务(task.cancel()),就必须把协程转成Task对象,这时候就需要用ensure_future(Python3.7+更推荐用asyncio.create_task,它是ensure_future的简化版,专门用于创建协程的Task)。处理非协程的Future对象:ensure_future还能接受Future对象(比如第三方库返回的Future),而gather虽然也能处理,但如果你需要统一处理协程和Future,ensure_future可以帮你做类型统一。
回到你的代码,因为生成任务列表后立刻就await gather,没有中间操作,所以两种写法效果完全一样,直接传协程更简洁,没必要多此一举。
内容的提问来源于stack exchange,提问作者adrian oviedo

