LEARN CLAUDE CODE

S08: Background Tasks — 后台执行

问题:阻塞操作锁死循环

核心循环是单线程的:user → LLM → tool → result → LLM。当工具调用是一个长时间运行的操作时(比如 npm installpytestdocker 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

BashToolrun_in_background 参数直接启用后台执行模式,返回一个任务 ID。TaskOutputTool 用于主动查询后台任务的输出——不需要等 drain,代理可以随时检查某个后台任务的进度和结果。

关键洞察:后台任务不是"多线程编程"——核心循环始终是单线程的。它只是把"等待子进程"这个操作从主线程移到了守护线程。模型的思考和决策仍然是串行的,但I/O等待可以并行。这是经典的异步I/O模式。