Appearance
asyncio 到底在调度什么:Coroutine、Event Loop 与 Awaitable
前置:ToolCall / ToolResult;基本 async / await
coro = fetch() 此时请求未必已发生;await coro 或 create task(coro) 后才开始推进。在 async Tool 里直接 time.sleep(5) 会让所有并发请求卡住;应使用异步 I/O 或移出线程/进程。
Mental Model
text
Coroutine → Task/TaskGroup → await/I-O suspension → resume/cancel/timeout → result/error异步正确性首先看生命周期和失败传播,而不是“用了 async def 就更快”。每个 child task 都应有 owner、取消路径和资源上限。
Coroutine
Coroutine 是调用 async def 得到的可暂停执行对象;它只有被 await 或包装成 Task 后才会由 Event Loop 推进,单纯创建 coroutine object 不代表代码已经执行。
Event Loop
Event Loop 是 asyncio 的调度器:监视 I/O、timer 和 ready Task,在 coroutine 遇到 await 暂停时切换到其他可运行任务。它提供协作式并发,不等于多核并行。
任何长时间 blocking CPU/I/O 都会阻塞同一 loop 上所有 Agent task。
Awaitable
Awaitable 是可以出现在 await x 右侧的协议/对象类别,包含 coroutine、Task、Future 等。它表示“调用方可以等待其完成并取得结果”,并不等同于某一种具体调度实体。
最小实现 / 数据结构
python
sem = asyncio.Semaphore(8)
async def run_one(call):
async with sem:
async with asyncio.timeout(call.timeout):
return await call.execute()在 Shadow Harness 中怎么落地
ParallelToolExecutor 用 TaskGroup/Queue/Semaphore;Runtime policy 决定 partial failure,而不是盲从库默认。
Failure Modes
创建 Coroutine 但从未 await/调度
症状: 调用 async function 得到 coroutine object 后直接丢弃或当结果使用。
为什么危险: 代码实际没执行,业务状态却可能继续。
处理: 明确 await/create task,并让 task 有 owner。
在 Event Loop 中执行 Blocking I/O/CPU 重任务
症状: async def 内直接 requests/time.sleep 或长 CPU 计算。
为什么危险: 整个 loop 无法调度其他任务。
处理: I/O 用 async client;CPU/legacy blocking 放 thread/process executor 并限容量。
如何验证
| Case | 断言 |
|---|---|
| 创建 Coroutine 但从未 await/调度 | 调用结束无未 await coroutine,预期副作用确实发生。 |
| 在 Event Loop 中执行 Blocking I/O/CPU 重任务 | 并发短任务中插入 blocking call,优化后其他任务延迟不被整体拖住。 |
自测
- 一个 coroutine 被创建但从未 await/schedule,它和真正运行中的 Task 有什么区别?Event Loop 实际调度的是什么?
- 在 async 函数里直接做长时间阻塞 I/O,会怎样影响同一个 Event Loop 上的其他 Tool Call?