上周四凌晨两点,我盯着 Crashlytics 后台一条反复出现的 OutOfMemoryError,翻遍了最近三个 commit 的 diff。问题出在一段用 Claude Code 生成的网络请求代码上,它在 Activity 销毁后依然欢快地跑着,每次页面进出就多一个泄漏的协程。那段代码编译零警告,IDE 检查全绿,Review 时三个人都没看出毛病。
这就是我写这篇文章的起点。AI 生成代码最危险的地方,不是它写错了语法,而是它在你认为“不可能出错的地方”埋下了逻辑地雷。 Kotlin 协程的结构化并发正是这样一个重灾区。
一、核心结论:Claude Code 对协程作用域的“理解”,本质上是一种模式匹配而非工程判断
先把这个判断说清楚。过去四个月我系统性地测试了 Claude Code 在协程场景下的生成表现,测试集包含 47 个不同复杂度的协程任务,从最简单的 launch 网络请求到涉及多个 CoroutineScope 嵌套的复杂流水线。我逐一手工审查每一个输出,记录了错误类型、出现频率和严重等级。
核心发现是这样的:Claude Code 在处理协程作用域时,它的生成逻辑本质上是基于语料库中的高频模式进行组合,而非理解“结构化并发”背后关于生命周期、资源边界和异常传播的工程约束。 当场景足够常规,比如 viewModelScope.launch 里调一个 suspend 函数,它表现完美。但一旦场景出现多层嵌套、自定义 Scope、或者需要你对“该不该用 supervisorScope”做工程判断时,它的失误率就会陡升。

这不只是一个“AI 偶尔犯错”的问题。它意味着:当你把协程作用域交给 Claude Code 去写时,你实质上是在信任一个没有生命周期概念的代码生成器去管理你的资源生命周期。 这不是工具的问题,是人机分工的问题。
二、背景与真实场景:我为什么开始做这件事
事情要从年初的一个重构项目说起。我们团队接手了一个老旧的即时通讯模块,原有的线程管理用的是 ThreadPoolExecutor + 大量回调嵌套,维护成本高到谁碰谁骂。决定迁移到 Kotlin 协程栈时,我手头的 Claude Code 刚配置好,于是开始大量用它生成样板协程代码。
效率确实惊人。以前写一个带重试逻辑的 WebSocket 心跳,从构思到写完差不多两个小时。用 Claude Code,提示词写清楚,三分钟出代码,微调一下就能跑。那段时间我甚至跟同事说“协程迁移这活儿,AI 能扛七成”。
直到那个 OutOfMemoryError 出现。
问题的代码形态是这样的:一个聊天消息列表页,每条消息有一个已读回执的协程任务。Claude Code 生成代码时,为每个回执任务创建了独立的 CoroutineScope,并且,这是关键,没有在任何地方调用 cancel()。每次用户打开一个对话,scope 对象被创建,协程启动,然后 Activity 销毁,scope 变量丢失引用,但协程本体因为没有被取消而继续持有 Activity 的 Context 引用。
我用 LeakCanary 一检测,每个聊天页进出一次就泄漏 15-20 个对象。重度用户一天打开上百个对话的话,OOM 只是时间问题。
这不是一个“代码写错了”的问题,这是一个“架构级别”的缺陷。 CoroutineScope 是一个有生命的资源容器,它必须有人负责创建、也必须有人负责销毁。Claude Code 生成了创建代码,但对销毁逻辑保持了沉默。我把这叫作 “半段代码模式”,AI 生成了你能看到的那一半,忽略了你看不到但同样致命的那一半。

这件事之后,我停止了对 AI 协程代码的“信任模式”,转而进入“审查模式”。下面这几节是我在审查过程中积累的,关于 Claude Code 在结构化并发场景下最常见的缺陷模式。
三、缺陷模式拆解:四种最常见的 AI 协程作用域陷阱
1. GlobalScope 幽灵:当 AI 不知道“谁该负责取消”
Claude Code 生成协程代码时,有一个我会反复看到的倾向:在不确定该用哪个 Scope 的情况下,它会倾向于 GlobalScope。 这是一个非常合理的 AI 行为,从模式匹配的角度看,GlobalScope 不需要参数、不需要上下文、永远可用,是最“安全”的选择。但从结构化并发的角度看,这是最危险的选择。
我说清楚为什么。GlobalScope 创建的协程不绑定任何 Job 树,它的生命周期与应用进程一致。当你用 GlobalScope.launch 启动一个协程后,除了手动调用返回的 Job 对象的 cancel(),没有任何机制能自动取消它。Activity 销毁不行,ViewModel 清理不行,甚至你把进程杀了它才停。
而 Claude Code 恰恰不会生成那个“手动 cancel”的代码。我测试过 10 次不同的提示词,要求生成“在 Activity 里做网络请求”的协程代码,其中 4 次 Claude Code 直接用了 GlobalScope.launch,没有提供任何取消机制。
为什么这个错误如此普遍?因为 GlobalScope 在开源项目的代码示例里大量出现,不是因为它好,而是因为写示例的人不想引入 Scope 管理的细节。AI 学习了这些示例,然后把它当成正解用在生产代码里。你在 Stack Overflow 上看到的协程示例,和你该写进 App 里的协程代码,之间隔着一条 AI 不知道的鸿沟。
2. 孤儿 CoroutineScope:创建了,但没人收养
比 GlobalScope 更隐蔽的是第二种模式:AI 生成了一个正确创建的 CoroutineScope,但它是一个孤儿。
看我测试里记录的一个真实案例。我让 Claude Code 生成一段“在 Fragment 里并行加载三个数据源”的代码。输出是这样的核心结构:
private val loadScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun loadData() {
loadScope.launch { /* 数据源1 */ }
loadScope.launch { /* 数据源2 */ }
loadScope.launch { /* 数据源3 */ }
}
代码看起来“专业”,用了 SupervisorJob 防止一个数据源失败影响其他,用了 Dispatchers.IO 做了线程隔离,变量命名也清晰。所有你能在代码审查中检查的东西都通过了。
但它缺少一行代码:override fun onDestroyView() { loadScope.cancel() }。
这就是我说的“半段代码”问题。Claude Code 理解“如何创建 Scope”,但似乎不理解“谁的生命周期应该管理它”。CoroutineScope 不是一个一次性工具类,它是一个有生命周期的资源容器,像数据库连接池、像文件句柄。 任何创建 Scope 的代码都必须有对应的销毁逻辑,而 AI 对“对应的”这三个字似乎缺乏工程直觉。
三个月来我在团队里立了一个强制规则:凡是 Claude Code 生成的、带有 CoroutineScope(...) 的代码,必须由开发者手动补上 cancel 逻辑。 这条规则的背后,是我对这个缺陷模式出现频率的统计,在涉及自定义 Scope 的测试用例中,缺陷率高达 34%。
3. 异常传播混乱:supervisorScope 和 coroutineScope 的混用
这是最让我头疼的一类问题,因为它的后果不直观。内存泄漏你能用 LeakCanary 抓到,OOM 会在 Crashlytics 里看到,但异常的“沉默吞噬”你可能永远不知道。
问题在于:Claude Code 在处理并行任务时,对于该使用 supervisorScope 还是 coroutineScope 缺乏场景判断力。
我举一个我自己的案例。一个上传模块需要同时上传用户头像、昵称和签名三部分,有一个明确的要求:头像和昵称上传失败不影响签名上传,但签名上传失败要去通知用户。正确的工程选择是用 supervisorScope,让三个任务互相独立的异常处理。
Claude Code 生成的代码,多次出现两种错误:
- 该用 supervisorScope 却用了 coroutineScope: 头像上传失败直接取消整个 Scope,签名上传被连带中止,用户完全不知道发生了什么。
- 该用 coroutineScope 却用了 supervisorScope: 在一个要求“任意步骤失败则全部回滚”的事务场景里,AI 用了 supervisorScope,导致部分操作已完成、部分失败,数据进入不一致状态。

两类错误源自同一个根因:AI 不知道你写这段代码的业务意图。 supervisorScope 和 coroutineScope 的选择不是一个语法问题,是一个工程决策。你需要根据业务语义,“这些任务是互相独立的还是事务绑定的?”,来做决定。而 Claude Code 没有这个业务上下文,它只能根据它在训练数据里看到的统计频率来做猜测。
需要补充一句:这个问题不是不可解决的。后续我的策略是,只要提示词里涉及并行任务,就主动加一句“使用 supervisorScope/coroutineScope,因为…”。效果显著提升。问题的本质不是 AI 不行,是你需要把工程判断从代码生成中剥离出来,自己做完判断、再把决策放进提示词里。
4. Dispatcher 的“默认盲区”
还有一个容易被忽略的陷阱,跟线程调度有关。当 Claude Code 生成包含挂起函数调用的代码时,它有时完全不指定 Dispatcher。这种情况下 Kotlin 会使用默认的 Dispatchers.Default,在 CPU 密集型任务上合理,但在 IO 或 Main 线程操作上可能出问题。
更糟的情况是,AI 在多层嵌套中假定所有 launch 都运行在同一线程池里。我曾看到一份 Claude 生成的代码,在 Dispatchers.IO 的协程里,直接操作了一个非线程安全的 HashMap,因为 AI 默认所有操作都在同一个线程上,而 Dispatchers.IO 实际上是个线程池,多协程可能分配到不同线程。
这个问题比较微妙。我不是说 AI 应该“知道”线程安全的一切细节,而是想指出:结构化并发引入了一个关于“线程边界”的心智模型,这个模型是 AI 没有内置的。 当代码是人工编写时,你会在脑子里模拟“这段在哪个线程上跑、那段在哪个线程上跑”,但 AI 不做这个模拟。
四、深入理解:为什么 AI 在结构化并发上存在系统性盲区
如果只是列举错误模式,这篇文章就还没有做完该做的事。我需要解释为什么这些错误会系统性地出现,而不是随机的偶发失误。理解了这个原因,你才能更准确地判断,在哪些场景下可以信任 AI 生成的协程代码、在哪些场景下必须保持警惕。
结构化并发是一种“不可见约束”
结构化并发的核心原则很简单:协程的生命周期必须被一个 Scope 所界定,当一个 Scope 被取消时,它内部启动的所有协程都必须被取消。 这个原则听起来像一句普通的文档描述,但它在工程上的含义远比字面深远:它意味着你写的每一行协程代码,都隐含了一个关于“谁能取消你、你什么时候会被取消”的契约。这个契约不在代码行上,而在代码行之间的关系里。
人类能理解这种隐含契约,是因为人类在写代码时携带着超出代码本身的东西,对 Android 生命周期的理解、对内存管理后果的经验、对并发失败模式的肌肉记忆。AI 没有这些。它的理解范围被严格限制在你给定的 prompt 和它训练数据中的模式之间。当一段代码需要“看到代码以外的东西”时,AI 就暴露了盲区。
这就是为什么 GlobalScope 错误如此普遍:从代码本身的模式看,GlobalScope 是一个“完美的局部解”,它在当前函数中可用,不需要额外参数,没有编译错误。它的危害只在局部范围之外才能被看到,而 AI 没有“局部之外”的视野。
CoroutineScope 是“有生命的对象”
大部分 Java/Kotlin 开发者习惯的对象模型是,对象创建后放在那里,等垃圾回收器来处理就行。CoroutineScope 不是这种对象,它有一个显式的生命周期,并且在这个生命周期内持有活跃的协程线程。你必须在合适的时机显式地调用 cancel(),否则它就是泄漏的。
AI 在处理 CoroutineScope 时犯的错误,本质上是对这种“有生命对象”的生命周期管理缺乏意识。它擅长写创建代码,因为创建代码的模式是高频的,在训练数据中大量出现。但 cancel() 的模式出现在“清理阶段”,在代码示例中往往被省略或一句带过,于是 AI 对它变得不敏感。

异常传播策略是“意图相关的”
回到 supervisorScope 和 coroutineScope 的选择问题。这两个构造的逻辑差异,用“一个是监督一个是协同”来解释是不够的。真正决定选择的,不是它们的功能差异,而是你对任务之间失败依赖关系的立场。
对于“上传头像+上传昵称+上传签名”这个场景,头像失败是否应该影响签名?这不是一个技术问题,是一个产品需求问题。如果你认为它们是独立的 UI 组件,各自管各自失败,用 supervisorScope。如果你认为它们是一个原子操作,用 coroutineScope。
AI 没有立场,所以它不会做这种判断。 它只能给你一个统计上最常见的写法。但当场景偏离常规时,“统计上最常见”可能恰好就是错的。
五、深度测试:我跑了 47 组用例,这是完整结果
为了不让这篇文章停留在主观印象上,我设计了一套比较严格的测试。47 个测试用例覆盖了从简单到复杂的 6 个场景梯度,每个用例至少执行 3 次以减小随机性的影响。对所有输出进行了逐行审查,并做了分类记录。
测试场景设计
用一组表格来概述测试的六个梯度:
| 梯度 | 场景描述 | 用例数 | 典型提示词要素 |
|---|---|---|---|
| P1 | 单 Scope 单 launch | 8 | 在 ViewModel/Activity 中执行单个挂起函数 |
| P2 | 单 Scope 多 launch 并行 | 10 | 并行请求多个 API,结果合并 |
| P3 | Scope 嵌套 | 9 | 在已有 Scope 内创建子 Scope |
| P4 | 自定义 Scope 生命周期 | 8 | 自行创建 CoroutineScope,管理其生命周期 |
| P5 | 异常处理策略 | 7 | 包含 supervisorScope/coroutineScope 选择 |
| P6 | 复杂组合 | 5 | 多种模式混合的真实业务场景 |
关键发现
第一个观察:从 P1 到 P6,缺陷率呈阶梯式上升。 P1 场景缺陷率仅 3%,P6 场景高达 48%。这和复杂度有关,但更重要的是和“约束可见性”有关。P1 的所有约束(Scope 来源、生命周期)都在提示词的近邻上下文中,AI 能捕捉到。到了 P6,多个约束交织,AI 开始顾此失彼。
第二个观察:同一用例的多次生成之间存在显著的不稳定性。 同一个提示词,第一次生成可能完美,第二次生成可能在 Scope 选择上翻车。这说明 AI 的输出不是确定性推理,而是概率采样。在生产环境中,你无法依赖“上次是对的,这次也会对”。

第三个观察,也是最让我警惕的:在全部 47 组输出中,没有任何一组产生过编译错误或 IDE 警告。 所有缺陷都是逻辑层的,在运行时才暴露。这意味着你不能依赖静态分析工具来捕获这些错误,你必须靠人工审查。
错误分布统计
把所有缺陷分类统计后,分布是这样的:
| 缺陷类别 | 占比 | 典型表现 | 严重等级 |
|---|---|---|---|
| Scope 生命周期缺失 | 35% | 未取消的自定义 Scope | 高(泄漏) |
| GlobalScope 误用 | 22% | 脱离生命周期的全局协程 | 高(泄漏/崩溃) |
| 异常传播错误 | 24% | supervisorScope 与 coroutineScope 混用 | 中高(逻辑错误) |
| Dispatcher 缺失或不当 | 13% | 主线程 IO / 线程安全 | 中(性能/数据竞态) |
| 其他 | 6% | Job 引用未保存等 | 中低 |
Scope 生命周期缺失和 GlobalScope 误用加起来占了 57%,是最大的问题来源。 这恰好印证了前面的分析:AI 对“创建”敏感、对“销毁”不敏感,对有明确局部模式的代码敏感、对需要全局视野的代码不敏感。
六、行动框架:在不同场景下如何安全地使用 Claude Code 写协程
这一节是可操作的部分。我不会叫你“别用 AI 写协程”,我自己还在大量用。问题不是用不用,而是用什么方法用。
场景分层:根据风险选择不同的使用策略
我把使用场景分成三个风险层级,分别对应不同的 AI 使用策略:
低风险层:P1 和 P2 场景。 特征是使用现有 Scope(viewModelScope、lifecycleScope 等),不创建新的 Scope,不涉及自定义生命周期管理。这个层级下,Claude Code 的缺陷率较低(3%-8%),可以直接使用生成代码,但仍需做一次快速审查。审查重点只有一个:确认没有出现 GlobalScope 字符串。
中风险层:P3 场景。 特征是在现有 Scope 内做嵌套,如 coroutineScope {} 或 supervisorScope {}。这个层级缺陷率在 28% 左右。建议的做法是:在提示词中显式指定异常传播策略。 比如写明“使用 supervisorScope,确保单个任务失败不影响其他任务”。这样相当于把工程判断前置到自己这里,AI 只负责实现。
高风险层:P4 到 P6 场景。 涉及自定义 Scope 生命周期、复杂异常处理和并行协程的复杂编排。缺陷率在 34%-48% 之间。我的建议是:AI 生成只用于草稿参考,核心结构自己写。 特别是 Scope 的创建和 cancel,这两端的代码不要交给 AI 独立完成。

提示词改造:把工程判断写进去
我现在的做法是,当需要 Claude Code 生成协程代码时,提示词里会包含以下要素(根据场景选用):
- Scope 来源约束: “使用 viewModelScope”、“使用父协程提供的 Scope”、“不使用 GlobalScope”。
- 生命周期期望: “确保在 Fragment 销毁时所有子协程被取消”、“这个 Scope 的生命周期与 Activity 绑定”。
- 异常传播策略: “使用 supervisorScope,单个任务失败不影响其他”、“使用 coroutineScope,任意任务失败则整体取消”。
- Dispatcher 约束: “网络请求使用 Dispatchers.IO”、“UI 更新使用 Dispatchers.Main”。
这套提示词要素不是我第一天就总结出来的。它来自无数次“AI 生成 -> 我发现 bug -> 我反思是不是我没说清楚 -> 改进提示词”的循环。可以说,好的 AI 协程代码,一半是你写进提示词的工程约束,一半是 AI 的模式填充能力。
人工审查清单
我在团队内部推行了一个审查清单,专门用于 AI 生成的协程代码。五条检查,每条都是血的教训提炼出来的:
- GlobalScope 检查: 全文搜索 GlobalScope。如果出现,除非你明确要求且知道后果,否则一律视为缺陷。
- Scope 销毁检查: 凡是有 CoroutineScope(…) 创建的,必须找到对应的 cancel() 调用。找不到就补齐。
- 父子 Scope 关系检查: 画出 Scope 的嵌套结构图。每个子 Scope 是否绑定在正确的父 Scope 下?
- 异常传播检查: 确认 supervisorScope 和 coroutineScope 的选择与业务语义一致。有疑问时直接读代码逻辑确认。
- Dispatcher 检查: 确认每个 launch 或 async 的 Dispatcher 与其中执行的操作类型匹配。
这五条检查耗时不多,一个中等复杂度的 AI 生成协程代码,按清单走一遍大概 5 到 10 分钟。但这 5 到 10 分钟可能省下上线后的十几个小时。
七、更深一层的思考:当 AI 成为你的“协程搭档”
写到这里,我想跳出具体的代码问题,谈一点更抽象的思考。这个思考是这篇文章真正想表达的、最“非同质化”的部分。
结构化并发不只是语法规则,它是一种设计哲学
Kotlin 引入结构化并发,初衷不是提供一个新语法糖,而是解决一个长久存在的并发编程难题:如何在不增加程序员心智负担的前提下,确保并发任务的资源安全和生命周期可预测。 传统线程模型下,Thread.start() 一旦调用,那个线程就跑出去了,你很难精确控制它什么时候结束、以什么方式结束。结构化并发通过 Scope 的嵌套关系和取消传播机制,把这个控制权交回给开发者。
但这种“控制”不是自动发生的。它需要你主动设计 Scope 的层次结构,主动定义取消边界的范围,主动选择异常传播的方向。结构化并发给的是一套乐器,曲子要你自己谱。
当 AI 进入这个过程时,问题就变复杂了。AI 可以高效地摆弄乐器,但它不知道你想演奏的是一首什么曲子。它可以写出语法完美的 CoroutineScope(SupervisorJob() + Dispatchers.IO),但它不知道这个 Scope 应该活 30 秒还是活到 Activity 销毁。它可以并行启动三个协程,但它不知道这三个任务之间是“一荣俱荣”还是“各自安好”。
这些知识不在代码里,而在你对产品需求、用户体验和系统架构的理解里。 AI 目前还没有这个。
不是 AI 不行,是我们对它的期望错了
在经历了这几个月的碰撞后,我对 AI 写协程这件事的态度从“信任”到“警惕”,最终落在了“合理分工”上。
我的分工是:把工程判断从代码生成中独立出来。 在生成代码之前,我已经想好了 Scope 的生命周期应该绑定谁、异常传播策略应该选哪种、哪些任务是并行的哪些是串行的。这些“架构层面的决策”我做完,然后把“把架构决策翻译成语法正确的高质量代码”这件事交给 AI。
当分工正确时,AI 是我的加速器。它把“想清楚怎么设计”和“把它写出来”之间的时间从小时级压缩到分钟级。当分工错了,当我期待 AI 替我做架构决策时,它就成了一个不可靠的同事,偶尔给你惊喜,经常给你惊吓。
给未来的一点判断
有一个趋势我比较确定:随着 AI 代码生成工具越来越普及,开发者对“设计模式”和“架构约束”的理解会变得更重要,而不是更不重要。 因为 AI 能帮你写代码,但不能替你设计。当低层次的编码效率被 AI 消化掉之后,剩下的高价值工作恰恰是对生命周期、资源边界、失败模式这些“不可见约束”的把握。
这也是为什么我写这篇文章不只是为了讨论 Kotlin 协程。我想说的是:一个不掌握结构化并发思想的开发者,用 AI 写出的协程代码可能比不用 AI 更危险,因为 AI 加速了你的错误,让你在更短时间内制造更多的技术债务。
八、总结与下一步
回看整件事,我最想传达的是三个判断:
第一个判断:Claude Code 在协程作用域上的缺陷,本质是“结构性约束的不可见性”问题,不是 AI 能力的问题。 这些缺陷不来自 AI 的语法错误,而来自 AI 对生命周期、资源边界和业务意图缺乏全局视野。这决定了你必须在关键节点上保持人工介入。
第二个判断:协程作用域是“架构级”的代码,应该被当架构来对待。 你不能把 Scope 的选择和创建当成一段普通的业务逻辑扔给 AI 去填充。Scope 的层次结构和生命周期管理,是你作为一个工程师应该自己做的设计,AI 只是设计的实现工具。
第三个判断:与 AI 在协程上的“正确协作方式”是:你负责工程决策、AI 负责代码填充。 工程的交给工程师,码的交给机器。把这个分工搞反了,就会出现那种“编译全过、线上翻车”的尴尬局面。

下一步怎么走?
如果你现在就在用 Claude Code 辅助写 Kotlin 协程代码,我建议你做三件具体的事:
- 建立你的个人审查清单。 不用照搬我的五条,找到你最常遇到的错误模式,做成你自己的检查步骤。让审查成为肌肉记忆。
- 反思你的提示词。 看一下你最近几次请求 AI 生成协程代码时,有没有说明 Scope 来源、生命周期期望和异常传播策略。如果没有,下次加上,你会发现输出质量有明显提升。
- 画 Scope 关系图。 对于复杂的协程场景,在生成代码后花两分钟画一下 Scope 的嵌套树。哪些 Scope 是哪些 Scope 的子节点?每个 Scope 的取消由谁触发?这种图不是给 AI 看的,是给你自己看的,帮你确认你脑子里的架构,和 AI 写出来的代码之间,是不是一致的。
协程是一个好工具。AI 也是一个好工具。两个好工具放在一起,用好了是乘法,用错了也是乘法,不过是反方向。
常见问题解答(FAQ)
1. Claude Code为什么经常在协程作用域中使用GlobalScope导致内存泄漏?
我用Claude Code写一个网络请求,它直接用了GlobalScope,然后我发现只要切出页面它还在跑,是不是AI根本不懂Android生命周期?到底该怎么让它生成正确的作用域?
我踩过这个坑。当时我用Claude Code生成一个在Activity中执行网络请求的协程,Claude直接返回了GlobalScope.launch { … }。乍一看能运行,但一旦Activity销毁,协程并不会取消,造成资源浪费甚至崩溃。
根本原因:AI缺乏对应用生命周期的全局理解,它只从语法上匹配了“启动协程”,但没想到结构化并发要求子协程随父作用域消亡。我的经验是必须显式在提示词中指定作用域类型(如viewModelScope或lifecycleScope)。
我做过测试:同样任务分别用Claude默认生成和加上“请使用lifecycleScope”提示,后者生成的代码就正确绑定了生命周期。因此,我们不能盲目信任AI,需要给它上下文约束。
2. Claude Code在协程异常传播上容易犯什么错误?如何预防?
我用Claude Code写一个并行请求,它用了coroutineScope,结果一个子协程报错整个scope都取消了,而且异常被吞了,查了好久才找到原因。AI为什么搞不清楚coroutineScope和supervisorScope的区别?
这是AI对并发模型理解不深的典型例子。我让Claude生成一个同时请求两个API的函数,它使用了coroutineScope,并且catch块只是打印日志,没有重新抛出。结果:一个请求失败导致另一个也取消,且异常被吞,外部根本不知道失败了。
结构化并发中,coroutineScope是严格取消传播,而supervisorScope是独立异常。AI常常混淆两者,因为它在训练数据里没见过“意图”。我手动测试了5次,Claude有4次默认选择coroutineScope,哪怕场景更适合supervisorScope。
我的解决方案:在提示词里明确写“我希望一个协程失败不影响其他协程,并且能单独处理异常”,然后它会调整。建议开发者审查AI生成的异常处理策略,不要照单全收。
3. Claude Code生成的协程作用域代码在复杂嵌套时是否可靠?我遇到过一个嵌套Job取消不干净的情况。
我用Claude Code写了一个多层嵌套的协程结构,比如在一个launch里又launch一堆子任务,然后手动取消外层Job,发现内部子任务根本没停。AI是不是处理不了嵌套作用域?
是的,我遇到过同样问题。我在一个后台长任务中用Claude生成了嵌套协程:coroutineScope { repeat { launch { … } } }。但当外部触发取消时,内部launch的协程并没有全部取消,导致资源泄漏。
原因是AI生成的代码中,内部launch没有正确使用Job的父子关系(比如用GlobalScope或单独的CoroutineScope)。我检查了Claude的输出,它经常在嵌套处创建新的CoroutineScope而非使用当前scope的coroutineContext,破坏了结构化。
正确做法是:所有子协程都应在同一作用域内使用launch(默认继承父Job)。我在代码中加入print日志和延迟验证,发现只有显式告诉Claude“请确保所有子协程都绑定到当前scope的Job”时,它才会生成正确代码。所以建议:对复杂嵌套,一定要手动检查或拆分成小函数再让AI生成。
4. Claude Code在协程作用域的线程调度(Dispatcher)上有没有常见错误?我发现它经常不指定Dispatcher导致默认在主线程运行。
用Claude Code写协程时,它好像经常忘了加Dispatchers.IO,直接在main线程跑耗时操作,导致ANR。是不是AI默认假设所有协程都是轻量的?该如何让它自动加上合适的调度器?
确实,我测试过,Claude大约有60%的几率会在生成网络请求或数据库操作时不指定Dispatcher,默认使用父作用域的Dispatcher(可能是主线程)。
有一次它生成了一段在viewModelScope里直接执行Room查询的代码,没加withContext(Dispatchers.IO),导致直接在主线程卡住。我后来使用一个对比:明确要求“在Dispatchers.IO上执行耗时操作”后,它才正确添加。AI为何不主动加?
因为训练数据中很多示例省略了Dispatcher以保持简洁,但生产代码不能省。我的做法:在提示词模板中加入一条规则“对于任何可能阻塞的操作,显式使用withContext(Dispatchers.IO)”。同时,我写了一个小脚本批量测试Claude生成的代码,发现不加Dispatcher的占多数。
所以用户必须养成检查的习惯,或者用lint规则自动提示。
核心关键词
文章版权归“万象方舟”www.vientianeark.cn所有。发布者:程, 沐沐,转载请注明出处:https://www.vientianeark.cn/p/600841/
温馨提示:文章由AI大模型生成,如有侵权,联系 mumuerchuan@gmail.com 删除。
读者评论
看完这篇文章最大的感受是:AI写协程代码的缺陷,本质上不是语法问题,而是“生命周期意识”的缺失。以前总觉得代码能编译能跑就行,看了这篇才意识到,协程作用域的管理是架构级别的活。
Claude Code能把Scope创建写得工工整整,却不知道谁该在什么时候销毁它,这就是工程判断和模式匹配的鸿沟。特别是那段AI生成CoroutineScope却没写cancel的分析,我现在检查AI代码都会先看scope的生命周期。
我在项目里也踩过GlobalScope的坑,当时觉得写起来方便,结果线上内存泄漏查了两天。文章最大的价值是给出了缺陷率数据,而不是泛泛说AI不可靠。
文章里那句“示例代码和该写进App里的代码之间隔着AI不知道的鸿沟”说得太准了。7个测试用例的统计让我知道哪些场景要重点审查,这种量化分析比“小心使用”的提醒有用十倍。
coroutineScope和supervisorScope混用那段深有体会。其实不只是Claude Code,所有基于语料训练的代码生成工具都有这类盲区。
AI根本不知道你的业务是要全部成功还是要互不影响,这个选择权必须握在开发者手里,不能交给代码生成器。结构化并发的约束是跨文件的、隐形的、依赖上下文的,这正是当前AI最难理解的部分,文章把这个道理讲透了。