当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio.as_completed 怎样按完成顺序处理结果

Python asyncio.as_completed 怎样按完成顺序处理结果

来源:17golang原创 2026-10-14 16:56:56 0浏览 收藏

批量请求多个接口时,最先完成的结果通常最有价值。如果仍然按输入列表逐个等待,前面的慢任务会挡住后面已经完成的任务。asyncio.as_completed() 的用途就是把 awaitable 按完成先后交给调用方。

官方文档:https://docs.python.org/3/library/asyncio-task.html#asyncio.as_completed

关键点是:as_completed() 改变的是结果的消费顺序,不会改变任务本身的并发执行方式;超时发生时,它也不会替你取消还没完成的任务。

为什么它能拿到先完成的结果

输入可以是一组协程、Task 或 Future。函数立即返回一个可迭代对象,迭代时每次取到“下一个已完成任务”的结果。输入顺序只决定任务集合,不决定输出顺序。

asyncio.as_completed 按完成顺序消费任务的静态结构说明图
图1:结构说明图,展示任务集合、完成通知与结果消费之间的静态关系,不是运行截图。

例如三个任务的预计耗时分别是 0.20、0.05、0.10 秒,消费顺序应当是第二、第三、第一。下面的数字只是示例输入,不能把它理解成固定性能承诺。

普通迭代写法兼容 Python 3.12 及更早版本

普通 for 迭代得到的是一个新的协程,需要 await 它才能取得下一个结果。这个协程不是原始 Task,所以如果需要知道结果属于哪个请求,应当在任务结果中携带标识。

import asyncio

async def fetch(name: str, delay: float) -> str:
    # 用延迟模拟不同请求的完成时间,实际项目中替换为 I/O 调用。
    await asyncio.sleep(delay)
    if name == "库存":
        raise RuntimeError("库存服务暂时不可用")
    return f"{name}完成"

async def main() -> None:
    tasks = [
        asyncio.create_task(fetch("详情", 0.20)),
        asyncio.create_task(fetch("库存", 0.05)),
        asyncio.create_task(fetch("推荐", 0.10)),
    ]
    try:
        # 每次 await 的都是最先完成的那个结果,不是列表中的下一个位置。
        for next_result in asyncio.as_completed(tasks):
            try:
                result = await next_result
            except Exception as exc:
                # 单个任务失败只影响本次消费,是否中止由业务决定。
                print(f"单项失败: {exc}")
            else:
                print(result)
    finally:
        # 如果外层提前退出,主动取消并等待剩余任务,避免留下悬挂任务。
        for task in tasks:
            if not task.done():
                task.cancel()
        await asyncio.gather(*tasks, return_exceptions=True)

asyncio.run(main())

这段代码的输出顺序大致会先看到“库存”的失败,再看到“推荐完成”和“详情完成”。重点不是把异常吞掉,而是把异常处理位置放在每次 await 附近,让其它已经排队的结果仍有机会被消费。

Python 3.13 及更新版本怎样保留 Task 身份

当前官方文档说明,Python 3.13 起 as_completed() 还可以作为异步迭代器使用。使用 async for 时,如果输入本来就是 Task 或 Future,迭代得到的就是这些原始对象,便于从映射表中找回请求名。

async def collect_by_task() -> dict[str, str]:
    tasks = {
        asyncio.create_task(fetch("详情", 0.20)): "详情",
        asyncio.create_task(fetch("库存", 0.05)): "库存",
        asyncio.create_task(fetch("推荐", 0.10)): "推荐",
    }
    results: dict[str, str] = {}
    # 异步迭代时拿到原始 Task,可以直接关联业务键。
    async for task in asyncio.as_completed(tasks):
        service = tasks[task]
        try:
            results[service] = await task
        except Exception as exc:
            results[service] = f"失败: {exc}"
    return results

如果项目还要兼容 Python 3.12 或更早版本,继续使用普通迭代,并让协程返回 (业务键, 结果);不要假设普通迭代返回的对象可以与原始 Task 做身份比较。

Python 3.13 异步迭代关联原始 Task 与业务键的结构说明图
图2:结构说明图,展示原始 Task、业务键映射与成功/异常结果的关系,不是运行截图。

timeout 只报告未完成,不负责清理

给 as_completed() 传入 timeout 后,如果规定时间内没有全部完成,迭代会抛出 TimeoutError。官方文档明确说明,剩余任务不会因此自动取消,它们仍可能继续运行。

async def collect_with_timeout(tasks: list[asyncio.Task[str]]) -> list[str]:
    values: list[str] = []
    try:
        # timeout 是整个结果集合的等待上限,不是每个任务各自的上限。
        for next_result in asyncio.as_completed(tasks, timeout=0.15):
            values.append(await next_result)
    except TimeoutError:
        # 只记录超时;下面仍要决定未完成任务是取消还是继续交给后台管理。
        print("结果集合未在截止时间内全部完成")
    finally:
        for task in tasks:
            if not task.done():
                # 当前调用方不再需要迟到结果,因此取消并等待清理。
                task.cancel()
        await asyncio.gather(*tasks, return_exceptions=True)
    return values

如果业务允许迟到结果继续写入缓存,就不要在这里取消,而是把剩余 Task 交给有生命周期管理的后台组件;如果请求已经结束,通常应取消并等待它们完成清理。

几个容易混淆的边界

问题结论
是否按输入顺序返回不是,按任务完成顺序消费。
单个任务抛异常怎么办在 await 该结果的位置捕获,是否继续由业务决定。
超时会不会取消剩余任务不会,需要调用方显式取消或转交后台管理。
怎样找回原始任务Python 3.13+ 用 async for;旧版本让结果携带业务键。

实际选择可以归纳为:只关心谁先完成时用普通迭代即可;需要把结果和原始 Task 关联时,优先使用 Python 3.13+ 的异步迭代;存在截止时间时,始终单独设计未完成任务的清理策略。

相关问题

as_completed 和 gather 有什么区别? gather 更适合一次等待并按输入顺序收集;as_completed 适合边完成边消费。

as_completed 超时后还能拿到迟到结果吗? 可以,但要保留 Task 引用并由其它生命周期管理逻辑继续等待;超时本身不会替你收集迟到结果。

为什么不能直接把普通迭代返回值和原始 Task 比较? 因为普通迭代返回的是用于取得下一个结果的协程,不是原始 Task;需要身份关联时使用异步迭代或在结果中带业务键。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Postman Console 怎样查看请求实际发送的 URLPostman Console 怎样查看请求实际发送的 URL
上一篇
Postman Console 怎样查看请求实际发送的 URL
Go encoding/csv 怎样处理带 BOM 的首行
下一篇
Go encoding/csv 怎样处理带 BOM 的首行
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    471次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    531次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    550次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    501次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    318次使用