异步任务、长任务、流式执行
这篇主要考察什么
大模型应用的一个现实问题是:很多任务根本不适合用用户发请求,服务同步返回结果这一种模式来做。
例如:
- 深度调研任务可能要跑几分钟;
- 多网页抓取和整理可能涉及几十次工具调用;
- 代码 Agent 可能要不断 build、test、patch;
- 多 Agent 协作可能存在多个远端等待点;
- 文件解析、批处理、报表生成、批量操作更不可能在一个同步请求里优雅完成。
所以,当面试官问长任务怎么做、异步怎么设计、流式输出怎么配合后台执行,本质上是在看你是否理解:Agent 不只是一个 HTTP handler,而是一个可能跨时间运行的交互系统。
可以在面试里这么表述:
:::color1
同步适合短、确定、结果马上可用的任务;异步适合长时间、可能中断、需要后台继续的任务;流式执行适合把中间进展持续反馈给用户。三者不是互斥关系:一个任务可以既是异步后台执行的,又对前端提供流式进度,还支持取消和恢复。
:::
为什么 Agent 特别需要异步与流式
1. 模型和工具链本身耗时不可忽略
即使单次模型推理不算太慢,一旦叠加:
- 检索;
- 网页抓取;
- 文件读取;
- 多次 tool loop;
- rerank;
- 多 Agent 协作;
- 审批等待;
整体时长很容易超出用户可接受的同步等待时间。
2. 用户对正在做什么很敏感
如果前端只是转圈,用户会非常焦虑:
- 它是不是卡死了?
- 到底在查什么?
- 有没有拿到部分结果?
- 需要我补充信息吗?
流式状态和阶段性反馈可以显著改善体验。
3. 长任务会遇到连接不可靠
网页关闭、网络抖动、移动端切后台都很常见。
如果任务只能绑在一次前端连接上,可靠性会很差。
所以必须把任务执行与前端连接分离开。
一图看懂三种执行形态
flowchart LR
A[用户请求] --> B{执行模式}
B --> C[同步请求-响应]
B --> D[异步后台任务]
B --> E[流式反馈]
D --> F[状态存储 / 任务表]
E --> G[前端阶段更新 / SSE / WebSocket / 增量消息]
D --> G
一句话解释:
同步是结果交付方式,异步是执行方式,流式是反馈方式。
三者可以组合,而不是只能三选一。
同步、异步、流式分别适合什么场景
1. 同步
适合:
- 简单问答;
- 少量工具查询;
- 结果可在几秒内稳定返回;
- 不涉及审批和长等待;
- 用户必须立即得到最终答案。
优点:
- 实现简单;
- 产品心智直观;
- 状态管理较轻。
缺点:
- 一旦任务变长,体验和可靠性都迅速变差;
- 不适合断点恢复;
- 难插入复杂等待。
2. 异步后台
适合:
- 长时间任务;
- 需要在用户离开页面后继续;
- 需要和队列、调度器、工作流结合;
- 可能依赖外部慢系统;
- 需要取消、恢复、回调通知。
优点:
- 执行与前端连接解耦;
- 更适合资源调度、超时、重试和恢复;
- 能承载真正的后台 Agent。
缺点:
- 需要任务状态存储;
- 用户如何得知进展要额外设计;
- 一致性和取消语义更复杂。
3. 流式反馈
适合:
- 任务过程本身对用户有价值;
- 需要逐步展示 reasoning phase、阶段进展或部分结果;
- 希望提升体感速度;
- 需要在任务很长时持续建立用户信任。
优点:
- 反馈快;
- 适合展示中间结果;
- 便于提示用户何时需要补充输入。
缺点:
- 前端和协议更复杂;
- 需要定义进度事件与断流恢复;
- 不是所有内容都适合实时暴露。
OpenAI 背景任务(Background Mode)可以怎么讲
这是当前很新的高频点。
你不需要背全部 API 细节,但建议知道以下几个要点。
1. Background Mode 解决什么问题
它把需要较长时间完成的模型任务从同步请求中拆出来,让任务在后台继续运行。
这非常适合:
- 深度调研;
- 长 reasoning;
- 多步工具调用;
- 需要几分钟的复杂任务。
2. 基本交互模型
典型模式是:
- 发起一个后台响应任务;
- 立即拿到任务标识或响应对象;
- 后续通过轮询状态、流式恢复或 webhook 获取完成结果;
- 必要时发起取消。
这个设计的关键价值是:
任务继续跑,但前端连接不需要一直悬挂。
3. Background 和 Streaming 可以组合
一个很值得你在面试里主动提的点是:后台执行不代表完全没有实时反馈。
它可以和 streaming 结合:
- 一边后台跑;
- 一边输出阶段性事件;
- 连接断了后还能从 cursor 继续取增量。
这体现了执行方式和反馈方式是两件事。
4. Cancel 要做成幂等操作
长任务一旦支持取消,就要考虑:
- 用户可能点两次取消;
- 前端和后端可能重复发取消;
- 任务已经完成后又收到取消。
因此取消动作本身也要设计成幂等且可安全落空。
5. Webhook 可以让任务完成通知更自然
对真正的后台任务来说,轮询虽然简单,但并不总是最优。
如果平台支持 webhook,任务完成、失败或需要人工介入时,可以主动通知应用侧。
这对于集成工作流和企业通知系统很有价值。
流式执行到底流什么
很多人一提 streaming,只想到模型 token 一点点吐字。
但在 Agent 系统里,更有价值的流式内容往往包括:
1. 文本增量
最直观的一种。
适合:
- 逐字输出答案;
- 展示草稿形成过程;
- 给用户体感上的即时反馈。
2. 阶段事件
例如:
- 已完成需求理解;
- 正在搜索网页;
- 已找到 12 个候选结果;
- 正在调用财务 Agent;
- 等待用户确认;
- 已进入最终生成阶段。
这种事件比纯文本 token 更有产品价值,因为它让用户知道系统当前在哪个阶段。
3. 工具事件
例如:
- 某个 tool call 发起;
- tool result 返回;
- 某个并行子任务完成;
- 某个远端 Agent 返回 artifact。
这类事件对调试和高级用户很有用,也适合作为 trace 的用户可见子集。
4. 部分结果 / Artifact 增量
例如:
- 已生成报告大纲;
- 已完成代码补丁初稿;
- 已汇总前 20 条调研结果;
- 已生成表格第一页。
这在长任务中很重要,因为用户经常不需要全做完才开始利用结果。
流式输出不等于暴露全部 Chain-of-Thought
这是一个需要小心说的点。
工程上,流式阶段更新和进度可见性非常重要,但不代表你要把模型的全部内部思维原样暴露给用户。
更合理的做法是输出:
- 阶段描述;
- 已执行动作;
- 可理解的中间状态;
- 用户需要知道的下一步。
也就是说,流式是产品反馈机制,不等于把内部推理全部公开。
长任务的核心设计:执行与呈现分离
这是非常重要的系统设计原则。
执行层负责
- 真正跑模型与工具;
- 维护任务状态;
- 写 checkpoint / trace;
- 处理重试、取消、超时和恢复;
- 记录 artifact。
呈现层负责
- 把当前状态映射成用户可见进度;
- 展示阶段消息、部分结果、最终结果;
- 处理断线重连;
- 订阅 webhook / polling / stream;
- 给用户提供取消、继续、补充输入入口。
如果你把这两层混在一起,例如执行线程直接维护前端长连接并把所有状态都绑死在连接上,系统会很脆弱。
Polling、Streaming、Webhook 怎么选
1. Polling
适合:
- 实现简单优先;
- 前端或调用方暂时不方便接 webhook;
- 任务频率不高;
- 能接受秒级状态刷新。
优点:
- 简单稳定;
- 容易调试;
- 很适合作为基础兜底机制。
缺点:
- 有额外无效请求;
- 状态更新不够实时;
- 轮询间隔要权衡时延与成本。
2. Streaming
适合:
- 用户在线等待时;
- 中间进度很重要;
- 希望体感更丝滑;
- 前端能稳定处理增量事件。
优点:
- 延迟低;
- 用户体验好;
- 适合多阶段 Agent 可视化。
缺点:
- 连接管理复杂;
- 需要处理断流和恢复;
- 前后端事件协议要设计好。
3. Webhook / Push Notification
适合:
- 任务可能很久;
- 应用侧是服务对服务集成;
- 需要被动接收完成通知;
- 不希望靠频繁轮询。
优点:
- 更节省查询流量;
- 适合后台集成;
- 与工作流和事件总线结合自然。
缺点:
- 需要安全验证;
- 回调失败要重试;
- 复杂度高于简单 polling。
成熟系统通常是:
- 在线场景用 streaming;
- 离线场景用 webhook;
- 始终保留 polling 作为保底查询。
取消(Cancel)为什么比想象中更复杂
很多人以为 cancel 就是停掉任务。
真实系统里,取消至少要回答:
- 当前任务是还没开始,还是正在执行,还是已经完成?
- 如果正在执行一个长外部调用,能否真正中断?
- 如果某一步已经产生副作用,取消后是否需要补偿?
- 如果任务是多子任务并行,取消是全取消还是局部取消?
- 取消请求重复到达时怎么处理?
所以一个成熟回答会说:
取消本质上是状态转移,不一定保证物理中止所有动作,但必须保证系统进入一致的 canceled / canceling 语义。
恢复(Resume)和断点续跑
异步长任务一旦存在,就必须考虑恢复。
恢复通常分三种:
1. 前端恢复观察
用户刷新页面后,继续查看任务当前状态和历史进度。
2. 执行恢复
Worker 崩溃或连接断开后,系统从 checkpoint 或持久状态继续执行任务。
3. 人工继续
任务进入 input-required,用户补充资料后,同一 task 再次继续。
你在面试里可以直接说:
查看状态和继续执行是两种不同恢复。前者恢复观察通道,后者恢复执行语义。
长任务里最容易被忽略的五个状态字段
这部分很适合拿来做工程化加分:
- phase:当前阶段,如 planning / searching / waiting_input / finalizing;
- progress:阶段进度,可选百分比或可读进度;
- last_event_id / cursor:用于流式恢复;
- cancel_requested:取消意图标记,避免硬杀一切;
- resume_token / task_id:恢复执行所需标识。
这些字段让长任务从只有一个 pending / done变成真正可管理的任务实体。
A2A、MCP 与异步流式的关系
A2A
A2A 天然适合异步和流式,因为远端 Agent 任务本来就可能持续很久,并且有 task state、artifact、push notification、resubscribe 等语义。
所以如果面试问多 Agent 长任务怎么做,A2A 是很好的切入点。
MCP
MCP 更常和能力接入相关,但在远程 HTTP 与事件流场景下,也会涉及长连接、流式事件、工具返回 UI 或任务状态等问题。
你不必把它讲得和 A2A 一样复杂,但要知道 MCP 也不是只能短平快同步调用。
常见误区
误区 1:异步就是把请求丢进线程池
太浅。
真正的异步任务需要状态存储、取消、恢复、通知和失败处理,而不是只把请求搬到后台线程。
误区 2:流式只是逐字输出
不完整。
在 Agent 里,流式更有价值的是阶段事件、工具事件和部分结果。
误区 3:长任务可以一直靠前端连接等着
不现实。
前端连接会断,用户会离开页面,所以必须把执行和连接解耦。
误区 4:支持取消就等于能立即停机
不一定。
很多外部调用无法瞬时中断,取消更常见的是让系统进入一致的取消状态,并停止后续步骤。
误区 5:有 streaming 就不需要 webhook / polling
不对。
流式适合在线观察,webhook 适合后台通知,polling 适合保底查询。三者往往是互补的。
高频面试题与回答模板
1. 为什么 Agent 特别适合异步执行?
标准回答:因为 Agent 任务经常是多步、多工具、可能很长、可能等待外部系统或人工输入。同步请求-响应模式不适合承载这类长生命周期任务。
2. 流式执行和异步执行是什么关系?
标准回答:异步是执行方式,流式是反馈方式。一个任务可以在后台异步跑,同时把阶段性进度流式推给前端。
3. 为什么长任务不能只靠轮询?
标准回答:可以用轮询兜底,但仅靠轮询会带来时延和无效请求。在线场景通常配合 streaming,后台集成通常配合 webhook。
4. Cancel 最重要的设计点是什么?
标准回答:把取消看成状态转移而不是简单杀进程,并处理重复取消、部分已执行副作用和后续补偿逻辑。
5. Resume 需要保存哪些信息?
标准回答:至少要有 task_id / run_id、当前阶段、最近事件 cursor、已完成步骤、已生成 artifact、等待输入状态和取消标记。
6. Background Mode 的价值是什么?
标准回答:它把长时间模型任务从同步请求中解耦出来,让任务能后台继续执行,并通过 polling、streaming、webhook 等方式获取进度和结果,更适合长 reasoning 和复杂工具链任务。
项目表达模板
我们的 Agent 有一些任务会跑很久,比如深度调研和批量自动化处理,所以我们没有把所有执行都绑在一次前端请求里。系统会把长任务提交到后台运行时,生成 task 状态并持续写入 phase、progress、artifact 和 trace。用户在线时,我们通过流式事件返回阶段进展和部分结果;用户离线时,可以靠 webhook 通知或后续轮询恢复查看。取消操作只修改任务状态并停止后续步骤,已执行副作用则通过补偿逻辑处理。这样执行层和呈现层被彻底分开,系统既能长时间运行,也能保持良好交互体验。
小结
把这篇压缩成四句话:
- 同步适合短任务,异步适合长任务,流式适合持续反馈;
- Agent 的长任务本质上要求执行与连接分离,不能绑在一个前端请求上;
- Polling、Streaming、Webhook 不是替代关系,而是不同场景下的组合;
- 取消、恢复、进度、部分结果和任务状态,是长任务设计的核心。
如果你能围绕这四句话讲清后台执行、流式事件、取消恢复和任务状态字段,这一题就已经非常像真正做过系统的人在回答。