Monty异步编程完全指南:async宿主函数、await与asyncio.gather并发执行
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
Monty 是一个用 Rust 编写的极简、安全 Python 解释器,专为 AI 生成代码而设计。它的异步编程模型让沙箱代码可以安全地并发调用宿主函数:通过AsyncMonty、await与asyncio.gather,你可以把网络请求、数据库查询等耗时操作放到宿主机上并行执行,而不需要容器或线程。
为什么 Monty 需要异步模型
传统沙箱靠容器隔离,启动慢、资源重。Monty 走的是另一条路:沙箱不直接碰外部世界,任何外部交互都必须通过开发者显式提供的宿主函数(Host Function)完成。
对于耗时较长的操作(比如fetch一个网页),同步阻塞会白白占用整个执行流。Monty 的解法是:
- 沙箱内部的
async def/await语法与 Python 一致 - 沙箱里没有事件循环,宿主就是事件循环(the host is the loop)
asyncio.gather让多个宿主调用真正并发运行
这个设计保证了:既拿到并发性能,又保持零信任隔离。🔒
快速上手:AsyncMonty + 异步宿主函数
只需三步:
- 使用
AsyncMonty代替同步的Monty - 在
external_lookup中传入协程函数(async def定义的宿主函数) - 沙箱代码用
await或gather调用它们
from pydantic_monty import AsyncMonty async def fetch(url: str) -> str: return f'contents of {url}' code = "await fetch('https://example.com')" async with AsyncMonty() as pool: async with pool.checkout() as session: result = await session.feed_run(code, external_lookup={'fetch': fetch})⚠️ 注意:同步版
Monty不能驱动协程宿主函数,必须用AsyncMonty,或改用快照机制手动驱动挂起。
更完整的入门示例见 docs/quickstart/python.md。
asyncio.gather:让多个宿主调用真正并发
asyncio.gather是沙箱内的并发核心。它接收多个可等待对象,同时发起宿主调用并返回结果列表(顺序与参数一致,而非完成顺序)。
import asyncio results = await asyncio.gather(fetch('a'), fetch('b'))几个关键行为:
| 场景 | Monty 的行为 |
|---|---|
| 多个宿主调用 | 全部并发执行,结果按参数顺序返回 |
空参数gather() | 返回[] |
| 传入非可等待对象 | 抛TypeError |
支持*args解包 | ✅ 列表、元组均可 |
| 同一协程/future 传入两次 | 自动去重,函数体只执行一次 |
| 已完成/失败的 gather 再次 await | 返回缓存结果,或重复抛出缓存异常 |
gather的实现细节(任务状态机、结果槽位、去重逻辑)可以在 crates/monty/src/asyncio.rs 中看到,行为验证用例在 crates/monty/test_cases/async__gather_all.py。
并发模型:协作者式调度,没有抢占
理解 Monty 异步的关键一句话:并发是协作式、宿主驱动的。
gather会在所有分支都阻塞在外部调用时,把挂起的调用交给宿主- 宿主返回结果后,沙箱再恢复执行
- 没有抢占、没有线程、沙箱内没有调度器
这意味着两点实际影响:
- 协程是一次性的(single-shot):同一个协程对象 await 第二次会抛
RuntimeError。需要复用结果时,请保存结果而不是协程本身。 - 宿主回调内部若写了阻塞代码,会阻塞你自己的事件循环——
AsyncMonty移离事件循环的是 worker I/O,而不是你的回调。
asyncio 模块的边界:只有 run 和 gather
沙箱里的asyncio模块刻意做得很小,只暴露两个函数:
asyncio.run(coro)—— 运行协程直到结束asyncio.gather(*awaitables)—— 并发执行并收集结果(不支持任何关键字参数)
create_task、sleep、wait、Queue、Lock等均未实现(访问会抛AttributeError);async for、async with和异步推导式则在解析期直接拒绝。完整的差异清单见 limitations/asyncio.md。
另外两个值得知道的与 CPython 的差异:
- 未 await 的 gather 不会启动子任务:
asyncio.gather(boom())若丢弃结果,Monty 里什么代码都不会执行 - 失败的 gather 的兄弟任务推进受限:它们只会在宿主结果到达或其他任务挂起时恢复,因此某些边界场景的输出顺序与 CPython 不同
进阶:手动驱动挂起(快照)
如果不想让feed_run自动应答所有挂起,可以用feed_start拿到每一个挂起点(快照),记录、限流、审批,甚至序列化到数据库明天再恢复:
snapshot = session.feed_start('fetch(url)', inputs={'url': 'https://example.com'}) result = snapshot.resume({'return_value': 'page contents'})当沙箱内所有任务都阻塞在宿主 future 上时,会得到FutureSnapshot,可按call_id批量提交结果。详见 docs/snapshots.md。
相关文件速查
| 内容 | 路径 |
|---|---|
| 宿主函数完整指南 | docs/host-functions.md |
| asyncio 限制与差异 | limitations/asyncio.md |
| 核心异步类型(协程/Future/Gather) | crates/monty/src/asyncio.rs |
| 基础 await 用例 | crates/monty/test_cases/async__basic.py |
| gather 并发用例 | crates/monty/test_cases/async__gather_all.py |
| 异步宿主调用用例 | crates/monty/test_cases/async__ext_call.py |
| Python 快速上手 | docs/quickstart/python.md |
小结
Monty 的异步编程为「AI 写代码、宿主供工具」的场景提供了轻量又安全的并发方案:
- 用
AsyncMonty开启异步池,宿主函数写成async def - 沙箱内用
await单点调用,用asyncio.gather批量并发 - 记住沙箱无事件循环——宿主即循环,并发性能取决于你的宿主实现
- 需要暂停/恢复/审计时,用快照机制接管每一次挂起
掌握这四点,你就能在微秒级启动的沙箱里,安全地跑起 AI 生成的并发 Python 代码了。🚀
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考