Kotlin 协程设计思想(七):为什么 Kotlin 要设计 SupervisorJob 和 supervisorScope?

------ 从异常隔离到作用域设计,彻底讲透 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 协程真正的底层原理。

相关推荐
Full Stack Developme1 小时前
SpringMVC multipart 文件上传
java·开发语言
得一录1 小时前
ModuleNotFoundError: No module named ‘llama_index.llms
开发语言·人工智能
j7~1 小时前
【C++】类和对象(下)--详解之再探构造函数,友元,static成员,类型转换等
开发语言·c++·类型转换·友元·匿名对象·内部类·编译器优化
稷下元歌1 小时前
7天学会plc加机器视觉关于运动控制部份,配套视频在bib
开发语言·c++·git·vscode·python·docker·pip
薇茗1 小时前
【C++】 类与对象 基础篇
开发语言·c++·基础语法·类与对象
晚笙coding1 小时前
从零讲透 LangChain 输出格式化:让模型真的“能用”
java·开发语言·langchain
奋斗的小方1 小时前
Java进阶篇1-1:异常
java·开发语言·python
故渊at1 小时前
第一板块:Android 系统基石与运行原理 | 第五篇:Context 上下文与资源配置体系
android·人工智能·opencv·context·上下文·资源配置体系
sycmancia1 小时前
Qt——多页面切换组件
开发语言·qt