← 中文资源

简体中文版本

用信号量限制 asyncio 并发

通过 asyncio.Semaphore 限制同时在跑的任务数量,按输入顺序返回结果,并把每个任务的异常单独收集起来,避免一个失败取消整批。

阅读英文原文

功能说明

bounded_map 用 asyncio.Semaphore 把同时在执行的协程数量限制在 limit 以内。每个输入先包装成 guarded 协程,拿到信号量后才调用真正的 worker;asyncio.gather(..., return_exceptions=True) 会等待全部任务结束,因此单个任务失败不会取消其他任务。返回值是 Outcome 列表,成功项带 value,失败项带 error,调用方可以按自己的策略决定跳过、重试还是整体失败。

关键代码

信号量只包围真正的等待段,而不是结果处理:

gate = asyncio.Semaphore(limit)

async def guarded(item: T) -> R:
    async with gate:
        return await worker(item)

outcomes = await asyncio.gather(*(guarded(item) for item in items), return_exceptions=True)

return_exceptions=True 会把任务抛出的异常变成列表元素;如果任务被取消,仍应让取消向上传播。

使用注意

  • limit 必须大于 0,函数入口已经做了校验,避免 Semaphore 构造时报错。
  • 结果顺序与输入顺序一致,因为 gather 按传入顺序返回,而不是按完成顺序。
  • 该实现会一次性创建所有协程对象;条目数量极大时应分批提交,避免提前占用大量内存。
  • 单个任务失败不会停止其他任务,但会等待所有任务结束才返回,适合可以容忍部分失败的批处理。
  • 如果需要快速失败,可以在第一个错误出现时取消其余任务,并捕获 CancelledError 做清理。

验证方式

直接运行示例:编号 4 的请求会抛出 RuntimeError,输出中应有 6 条成功记录和 1 条失败记录。也可以把 limit 改成 1 或 8 观察总耗时变化;再临时去掉 return_exceptions=True 做对比,确认异常会立刻打断 gather。