Python中关于asyncio.gather()的问题与使用

    loop = asyncio.get_event_loop()
exerciseId = [1,2,3]
// once 为协程
tasks = [once(i) for i in exerciseId]

loop.run_until_complete(asyncio.gather(*tasks))

Expected: 执行顺序是 once(1),once(2),once(3)

In fact: 是无序的,譬如是 2,3,1 的顺序

于是去查看 asyncio.gather 源码的时候,发现这么一行代码:for arg in set(coros_or_futures):。这个函数对列表 tasks 先进行了一次 set()操作,而 set 在 python 里是通过 hash 实现的,所以导致的无序是么?

那现在我希望按列表里的顺序进行添加 Task 操作,有什么好的办法呢?


Python中关于asyncio.gather()的问题与使用

10 回复

同步运行就有顺序了?


asyncio.gather()asyncio 模块里用来并发运行多个异步任务的核心函数。简单来说,它接收一堆协程(coroutine)或任务(Task),然后同时启动它们,最后等所有任务都完成,把结果收集到一个列表里返回。

基本用法:

import asyncio

async def task1():
    await asyncio.sleep(1)
    return "任务1完成"

async def task2():
    await asyncio.sleep(2)
    return "任务2完成"

async def main():
    # 同时运行 task1 和 task2
    results = await asyncio.gather(task1(), task2())
    print(results)  # 输出: ['任务1完成', '任务2完成']

asyncio.run(main())

这里 task1task2 会并发执行,总共耗时约2秒(而不是3秒)。

关键点:

  1. 返回值顺序gather() 返回的结果列表顺序严格对应你传入任务的顺序,和哪个任务先完成无关。
  2. 错误处理
    • 默认情况下,如果任何一个任务抛出异常,gather() 会立即抛出那个异常,其他任务会被取消。
    • 设置 return_exceptions=True 可以让异常作为结果返回,而不是中断整个操作。
    async def faulty_task():
        raise ValueError("出错了")
    
    async def main():
        results = await asyncio.gather(
            task1(),
            faulty_task(),
            task2(),
            return_exceptions=True  # 异常会变成结果的一部分
        )
        print(results)
        # 输出: ['任务1完成', ValueError('出错了'), '任务2完成']
    
  3. 传入的是协程对象:记住要把协程函数调用(如 task1())传进去,而不是函数本身(task1)。

常见坑:

  • 如果你在 gather() 里直接传了一个已经创建但未 await 的协程对象,它可能不会按你预期的方式运行。通常直接传 coro() 就行。
  • 想中途取消所有任务?直接取消 gather() 返回的协程本身就行。

一句话总结:用 gather 来并发跑多个独立异步任务并收集结果,注意错误处理和传参方式。

如果想要按照顺序依次添加,那就依次实例化 asyncio.Task 就行了。


tasks = [asyncio.Task(once(i)) for i in exerciseId]

或者依次调用 ensure_future 也行,

tasks = [asyncio.ensure_future(once(i)) for i in exerciseId]

感谢~明天试试

不对。。今天

python 真是强行异步,晦涩啊

所有的异步都是增加逻辑负担的,所以最好的方式当然是同步的方式写异步

话说你用了协程这几个任务应该不需要有先后的依赖吧。

是的,协程之间没有先后的依赖。但是他们的完成需要两个参数,p1,p2.p1 就是问题描述中 exerciseId 列表内的元素,p2 是个常数。问题是,exerciseId 内第一个元素对应的就是 p2,之后 exerciseId 每迭代一次,p2 自增 1.
之前写同步代码时,就是迭代列表 exerciseId,执行函数,自增 1.但是异步之后,由于函数的执行不是按列表的顺序,而是无序的,就会导致 p1 p2 对应出错。
在 V 友给出方案之前,我只好建立个字典,提前把 p1、p2 映射关系算出来保存。

所以就是:也可以不需要有先后依赖,但是按顺序来的话,会比较省事,不用改原来的代码

哦哦,不过 Python 协程用起来感觉真别扭

回到顶部