先理解
刚接触这个知识点
补齐先备概念,读原理与反例,再用自己的话解释为什么。
从核心原理开始 →理解 → 实现 → 排错 → 取舍
考察异步并发、截止时间、取消传播与部分结果。
知识内容核对 2026-10-03 · 原题来源核对 2026-10-02
建议先理解:
Agent 循环的控制权与完成判据 →工具调用的结构、授权与业务契约 →按当前基础选择起点,也可以依次深入。遇到不熟悉的概念,先回到核心原理;完成后用知识练习检查理解。
LEARN · PRACTICE · REFLECT
先沿着原理、问答和迁移案例阅读。需要检查理解时,再切换巩固练习或展开个人记录。
核心知识 · 并行结果的交付条件与共享截止时间
先备概念:事件循环、结构化并发、请求超时
并行只缩短独立等待,不改变任务依赖与成功条件。全局截止时间约束全部尝试与退避;一个本地任务被取消,也不证明远端副作用停止,结果必须逐项保留并分类。
三个独立新闻查询可以返回缺一个地区的部分结果,三个发布前校验则可能必须全部通过。required 属性在任务开始时定义;错误发生后临时把失败项降成可选,会制造虚假的成功。依赖另一个工具输出的调用不能同时启动。
async 只是函数声明,里面直接调用阻塞 HTTP 仍会阻塞事件循环,让其他工具、超时和取消不能及时调度。使用真正异步 I/O,无法改动的阻塞调用放到受控线程或进程,并设置底层超时。线程中的操作未必能随协程取消。
Python gather 默认首个异常传播后,其他任务继续;TaskGroup 的失败会取消同组其他任务并等待清理。选哪种取决于全部成功或部分结果策略,不能以 API 名字猜行为。共享 deadline 用单调时钟计算,重试、排队与退避都消耗剩余时间。
先按依赖决定哪些可并行,再明确是必须全部成功还是允许部分成功。为整个任务设截止时间,并给每个工具分配剩余时间和并发额度。独立只读任务可保留成功结果;有依赖或写副作用的操作需要更严格的协调。取消本地等待不代表远端操作没发生,结果应分别标记成功、失败、超时未知和取消。
如果三个工具分别获取三个地区的新闻,可以接受带缺口的部分结果;若它们共同构成一次发布前校验,任何关键校验失败都应阻止发布。把 required 标记放进任务定义,不在异常发生后临时决定算不算成功。依赖另一个工具输出的调用必须等到输入已验证,不能为了速度硬并行。
任务入口计算绝对 deadline,每一步使用剩余时长,不在每次重试重置完整超时。使用并发上限限制同时占用的连接与配额,超时等待也计入预算。异步库的任务组、聚合等待和取消行为不同,应明确选用的语义;同步阻塞函数即使写在 async 函数里也可能阻塞事件循环。
结果封装为工具 ID、状态、数据、耗时、错误类型和是否需要核对外部动作。独立读任务单个失败不必丢弃所有成功结果;关键任务失败时取消未开始动作。已经发出的写请求进入回执核对,不能把 CancelledError 直接解释为业务取消成功。清理连接和释放并发槽放在确定能执行的清理路径。
模拟 A 在 100ms 成功、B 立即失败、C 一直等待,全局截止时间 500ms。验证程序有界退出,A 的有效结果保留,B 错误可见,C 不占用遗留资源。若要求全部成功,最终不得标记 succeeded。进一步让 C 在取消后完成远端写入,检验候选人是否区分本地协程状态和外部事实。
只读异步任务的最小演示;不保证远端写请求随本地取消而撤销。
import asyncio
async def ok():
return "result"
async def fail():
raise RuntimeError("unavailable")
async def slow():
await asyncio.Event().wait()
async def main():
tasks = {"a": asyncio.create_task(ok()), "b": asyncio.create_task(fail()),
"c": asyncio.create_task(slow())}
done, pending = await asyncio.wait(tasks.values(), timeout=0.02)
for task in pending:
task.cancel()
await asyncio.gather(*pending, return_exceptions=True)
for name, task in tasks.items():
if task in pending:
status = "timeout"
elif task.exception() is not None:
status = "failed"
else:
status = "succeeded"
print(name, status)
asyncio.run(main())
预期输出
a succeeded
b failed
c timeout沿着问题的前提和约束继续向下读。先理解参考解答,再尝试收起答案,用自己的话解释因果和取舍。
第 1 层async 函数里调用同步 HTTP 库有什么问题?
并行要求可让出执行权,函数语法并不能保证这一点。
同步库占住事件循环线程,其他协程无法获得调度,可能连超时都延迟。改用异步客户端,或把阻塞调用放入有容量限制的线程/进程并设置连接、读和整体超时;仅套 async 包装无效。线程取消通常不等于终止其网络请求,仍需核对在途写入。
第 1 层聚合等待遇到异常后其他任务还在运行吗?
错误处理改变并发任务生命周期,需要查真实库语义。
看选用的原语。gather 默认首个异常向外抛出,但其他 awaitable 不会因此自动取消;return_exceptions=True 会收集逐项异常。TaskGroup 遇到非取消异常会取消余下任务并等待。无论哪种都需明确结果收集、清理与在途副作用,不能假设抛异常等于全停。
沿着这个回答继续深入
第 2 层想保留 A 的成功结果,又在 B 失败时取消 C,怎样实现?
了解原语之后,需要把库行为转成应用交付策略。
保存各任务结构化结果,并按 required 规则决定何时取消。若 B 关键失败,记录 A 的已完成数据用于诊断,取消 C 并等待有限清理;若 B 可选,继续等 C 到全局截止。异常封装必须保留失败类别,不能把它作为普通数据后误判全部成功。
沿着这个回答继续深入
第 3 层C 吞掉取消异常继续运行,TaskGroup 能保证及时返回吗?
控制任务组不等于控制所有子代码及远端副作用。
不能。取消是协作机制,子任务吞掉信号或执行阻塞代码会拖住收尾。修正子任务取消处理,清理后传播异常;不可信或不可中断任务用进程隔离及外部超时。若已发出远端写请求,即使终止本地进程也需对账。
第 1 层工具重试为什么要复用剩余时间?
局部重试必须服从全局交付预算。
每次都重置完整超时会让总时间随尝试次数膨胀。用入口 deadline 减去当前单调时钟得到剩余量,退避、排队和连接建立都包含其中;余量不足时停止重试并返回清楚的部分状态。金额或远端效果未知时单独标记,不能统一显示未执行。
先找出改变的条件,再判断原方案中哪些前提仍成立。下面的案例是教学推演,便于将原理迁移到新问题。
改变的条件:独立数据读取变为发布门槛
延伸问题:慢检查超时可以先发布吗?
不能。任何 required 检查未通过都阻止发布,保留成功证据与失败原因供修复;新一轮重新检查可能失效的版本。性能优化可调整并发和检查范围,但不能在超时后偷偷修改成功定义。
保持不变的原理:并行不改变依赖与验收。
改变的条件:全部只读变成读写混合
延伸问题:到期后取消所有任务就结束了吗?
停止未开始操作,取消可取消等待,已提交写入进入结果核验。成功读结果可保存,但依赖该写结果的总结不能生成确定结论;回执迟到时按操作 ID 更新账本。读写混合需要业务状态与协程状态分开。
保持不变的原理:本地并发状态不是外部业务状态。
依据公开技术资料设计;参考资料支持技术机制,场景与评分标准为本站设计,不代表某公司面试原题。 新增问答与迁移案例用于原理讲解,来源核查与案例运行验证分别记录。
读完后可以对照这些标准解释原理、边界和取舍。掌握程度由你自评;需要进一步验证时,再完成下方小任务。
写伪代码并行调用三个只读工具,在 500ms 截止时返回逐项状态。