Kotlin 协程设计思想(七):为什么 Kotlin 要设计 SupervisorJob 和 supervisorScope?
2026/6/6 17:51:51 网站建设 项目流程

—— 从异常隔离到作用域设计,彻底讲透 Kotlin 协程的容错机制

前面几篇,我们已经讲了:

CoroutineContext ↓ Job ↓ Dispatcher ↓ launch / async ↓ Exception ↓ Structured Concurrency

到这里,其实还有一个非常经典的问题。

很多同学都会问:

SupervisorJob() supervisorScope()

这两个东西到底有什么区别?

甚至很多人觉得:

名字差不多。 功能差不多。 随便用一个就好了。

事实上,它们虽然都带:

Supervisor

但解决的是两个完全不同的问题。

甚至可以说:

SupervisorJob supervisorScope

是 Kotlin 协程设计思想中最容易混淆,也是最经典的一组设计。

今天就彻底讲透:

为什么 Kotlin 要设计两个 Supervisor?

一、先回忆上一篇

上一篇讲过:

普通 Job:

Parent │ ├── Child1 ├── Child2 └── Child3

如果:

Child1 崩溃

那么:

Parent Cancel ↓ Child2 Cancel ↓ Child3 Cancel

结果:

全家陪葬。

例如:

coroutineScope { launch { throw RuntimeException() } launch { delay(5000) println("还能执行吗?") } }

答案:

不能。

二、Google 发现问题

很多业务根本不是这样。

例如:

首页:

用户信息 Banner 推荐商品 消息通知

四个接口。

结果:

Banner:

404

于是:

用户信息没了 推荐商品没了 消息通知没了

合理吗?

显然:

不合理。

Google 觉得:

兄弟之间 应该互不影响。

于是:

SupervisorJob()

诞生。


三、SupervisorJob 到底是什么?

很多人以为:

SupervisorJob()

是一种特殊 Job。

其实上一篇已经讲过:

Job 是 CoroutineContext 的配置项。

那么:

SupervisorJob()

本质也是:

一种 Job 配置。

只不过:

普通:

Child 崩 Parent 崩 兄弟崩

Supervisor:

Child 崩 Parent 不崩 兄弟继续。

例如:

Parent │ ├── Child1 崩 ├── Child2 正常 └── Child3 正常

结果:

只有 Child1 挂。

四、为什么 ViewModelScope 用 SupervisorJob?

很多人不知道:

viewModelScope

内部 其实就是:

SupervisorJob() + Dispatchers.Main

为什么?

例如:

页面:

请求用户 请求Banner 请求消息 请求商品

如果:

Banner:

404

总不能:

整个页面协程全部取消。

所以:

Google:

兄弟互不影响。

因此:

SupervisorJob()

非常适合:

页面业务。

五、问题来了

既然:

SupervisorJob()

这么好。

为什么还要:

supervisorScope { }


六、supervisorScope 到底是什么?

注意:

上一篇讲过:

Scope = Job树根节点。

那么:

supervisorScope { }

其实:

不是:

配置。

而是:

创建新的Scope。

例如:

supervisorScope { launch { } launch { } }

内部:

自动创建:

SupervisorJob。

形成:

Supervisor Scope │ ├── Child1 ├── Child2 └── Child3

特点:

兄弟互不影响。

七、终于看懂区别

一句话:SupervisorJob:

我是一个 Job。

作用:

放进 CoroutineContext。

例如:

CoroutineScope( SupervisorJob() + Dispatchers.Main )

supervisorScope:

我是一个 Scope 构造器。

作用:

临时创建一个异常隔离作用域。

例如:

supervisorScope { }

所以:

SupervisorJob 是配置。 supervisorScope 是作用域。

八、项目里到底怎么选?

这个最重要。


如果 自己创建 Scope:

例如:

Repository Manager SDK

推荐:

CoroutineScope( SupervisorJob() + Dispatchers.IO )

如果:

当前 suspend 函数:

临时需要:

几个并发任务 互不影响

推荐:

supervisorScope { }

例如:

suspend fun loadHome() = supervisorScope { }

九、为什么 coroutineScope 和 supervisorScope 都存在?

例如:

coroutineScope { }

特点:

一人犯错 全家陪葬。

适合:

必须全部成功。

例如:

支付流程 登录流程 事务流程

而:

supervisorScope { }

特点:

互不影响。

适合:

首页数据 多个接口 多个独立任务。

十、突然和前面几篇串起来了

现在回头看:

CoroutineContext ↓ Job ↓ Dispatcher ↓ launch ↓ Exception ↓ Scope ↓ Supervisor

你会发现:

Google 从头到尾都在做一件事:

控制异常传播。

普通:

异常向上传播。

Supervisor:

异常到此为止。

这就是:

Kotlin 协程容错设计。

十一、最终总结

如果让我一句话解释:

SupervisorJob()

我会说:

一种 Job 配置。 用于隔离兄弟协程异常。

如果让我解释:

supervisorScope { }

我会说:

一种 Scope 构造器。 用于创建临时异常隔离作用域。

如果让我解释:

为什么要设计两个 Supervisor?

我会说:

一个负责配置运行环境。 一个负责构建作用域。

它们解决的问题不同,

但设计思想一致:

异常不要无限传播。

下篇预告

到这里:

CoroutineContext ✓ Job ✓ Dispatcher ✓ launch / async / withContext ✓ Exception ✓ Structured Concurrency ✓ Supervisor ✓

整个协程运行模型已经完整。

但是还有一个困扰无数 Kotlin 开发者的问题:

suspend 到底是什么? 为什么 suspend 不是开启协程? delay() join() await() collect() 为什么都是 suspend?

下一篇我们继续:

《Kotlin 协程设计思想(八):suspend 到底是什么?为什么 suspend 不是开启协程?》

从 Continuation、状态机到协程恢复机制,彻底讲透 Kotlin 协程真正的底层原理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询