LEARN CLAUDE CODE
S08: Background Tasks — 后台执行
问题:阻塞操作锁死循环
核心循环是单线程的:user → LLM → tool → result → LLM。当工具调用是一个长时间运行的操作时(比如 npm install、pytest、docker build),整个循环都被阻塞了。模型在等待工具结果返回,用户在等待模型响应,什么都做不了。
阻塞场景:
时间线 ──────────────────────────────────────────────►
主循环: [LLM调用] → [BashTool: npm install] ............等待 45s............. [结果返回] → [LLM调用]
▲ ▲
│ │
└── 整个循环在这里卡住了 ──────────────────────────────────┘
模型不能做任何其他事情
用户不能输入任何内容
解法:守护线程 + 通知队列
设计思路:不在主循环里等子进程结束,而是把它扔到后台守护线程执行。主循环立即收到一个"已启动"的确认,然后继续做其他事情。当后台任务完成时,结果进入一个通知队列。每次 LLM 调用之前,主循环会 drain(排空)通知队列,把所有完成的后台任务结果一次性注入上下文。
非阻塞场景:
时间线 ──────────────────────────────────────────────►
主循环: [LLM] → [启动后台npm install] → [确认:已启动] → [LLM] → [做其他事] → [drain队列] → [LLM拿到npm结果]
│ ▲
守护线程: └── npm install 在后台执行 ────────────── 完成 ──► 通知队列 ─┘
关键:主循环没有被阻塞,可以继续处理其他工具调用
单线程循环 + 并行子进程
这个设计非常巧妙——它保持了核心循环的单线程简单性,同时获得了并行执行的能力:
| 组件 | 线程模型 | 职责 |
|---|---|---|
| 核心循环 | 单线程(主线程) | LLM 调用、工具分发、上下文管理 |
| 后台任务 | 守护线程(每任务一个) | 执行长时间子进程,完成后写入通知队列 |
| 通知队列 | 线程安全队列 | 存放已完成的后台任务结果 |
| Drain 操作 | 在主线程中执行 | 每轮 LLM 调用前清空队列、注入结果 |
BashTool(run_in_background=true)
→
spawn 守护线程
→
返回 task_id
→
主循环继续
守护线程: 子进程完成
→
结果入队列
→
下轮 drain
→
LLM 读到结果
对比:同步 vs 后台
| 维度 | 同步执行(默认) | 后台执行(S08) |
|---|---|---|
| 主循环状态 | 阻塞等待 | 立即继续 |
| 并行度 | 一次一个工具 | 多个子进程同时运行 |
| 结果获取 | 立即返回 | 下一轮 drain 时注入 |
| 适用场景 | 快速操作(读文件、搜索) | 慢操作(安装、测试、构建) |
| 复杂度 | 简单 | 需要队列管理和 drain 机制 |
对应官方工具
BashTool (run_in_background) + TaskOutputTool
BashTool 的 run_in_background 参数直接启用后台执行模式,返回一个任务 ID。TaskOutputTool 用于主动查询后台任务的输出——不需要等 drain,代理可以随时检查某个后台任务的进度和结果。
关键洞察:后台任务不是"多线程编程"——核心循环始终是单线程的。它只是把"等待子进程"这个操作从主线程移到了守护线程。模型的思考和决策仍然是串行的,但I/O等待可以并行。这是经典的异步I/O模式。