------ 从 Thread、Future、Callback、RxJava 到 Coroutine,彻底讲透 Kotlin 协程的设计哲学
写在前面
经过前面九篇,我们已经讲了:
CoroutineContext
✓
Job
✓
Dispatcher
✓
launch / async
✓
Exception
✓
Scope
✓
Supervisor
✓
suspend
✓
Flow
✓
很多同学可能觉得:协程体系终于学完了。
但是还有最后一个问题没有回答。
甚至,这是整个 Kotlin 协程体系最大的一个问题:
Google 为什么要设计 Coroutine?
难道:
Java 不够好吗?
Thread 不够好吗?
Future 不够好吗?
RxJava 不够好吗?
今天,我们不再讲某个 API。
而是从整个异步编程的发展历史出发,看看 Kotlin 协程到底解决了什么问题。
一、最早只有 Thread
Java 最开始:
new Thread().start();
特点:
简单。
直接。
暴力。
但是问题很明显:
线程太重。
线程栈空间
系统调度
线程切换
创建销毁成本
如果:
10000 个任务
怎么办?
显然:
线程不能等于任务。
Google 开始思考:
任务
≠
线程
二、Executor 出现
于是:
ExecutorService
出现。
思想:
任务
↓
线程池
↓
线程
线程复用。
性能提高。
但是问题又来了:
Future。
Callback。
越来越复杂。
三、Future 出现
例如:
Future<User> future;
等待:
future.get();
问题:
get()
阻塞线程。
等待网络:
线程什么都干不了。
资源浪费。
四、Callback 横行
Android 开始进入:
Callback 时代。
例如:
login
↓
getUser
↓
getGoods
↓
getOrder
↓
updateUI
层层嵌套。
形成经典:Callback Hell。
代码越来越难维护。
五、RxJava 出现
RxJava 出现以后:
解决 Callback Hell。
例如:
map
flatMap
zip
merge
功能非常强。
但是:
学习成本越来越高。
Scheduler
Disposable
Subject
BackPressure
Google 又发现:
需要一种更简单的方案。
六、Coroutine 出现
Kotlin 提出了:
Suspend
核心思想:同步代码。异步执行。
例如:
以前:
login(){
getUser(){
getOrder(){
}
}
}
现在:
login()
getUser()
getOrder()
updateUI()
代码重新回到线性。
七、Suspend 解决什么?
一句话:
一次任务。
一次结果。
例如:
请求一次。
返回一次。
结束。
但是现实世界:
很多数据不是一次性的。
八、Flow 出现
例如:
定位
WebSocket
蓝牙
下载进度
特点:
连续产生数据。
Suspend:
解决不了。
于是:
Flow 出现。
九、StateFlow、SharedFlow、Channel
Flow 很好。
但是:
状态怎么办?
事件怎么办?
消息怎么办?
于是:
StateFlow
管理状态。
SharedFlow
管理事件。
Channel
管理消息。
callbackFlow
管理回调接入。
整个体系越来越完整。
十、突然发现一条主线
很多人觉得:
Google 一直在增加 API。
其实不是。
Google 一直在解决不同的问题。
Thread:
怎么执行?
Executor:
怎么复用?
Future:
怎么等待?
Callback:
怎么通知?
Coroutine:
怎么暂停恢复?
Flow:
怎么处理连续数据?
StateFlow:
怎么管理状态?
SharedFlow:
怎么管理事件?
Channel:
怎么排队消息?
callbackFlow:
怎么兼容传统回调?
突然发现:
整个体系非常统一。
十一、终于理解 Kotlin 协程
很多教程都会说:
Coroutine
轻量级线程。
我觉得:
这句话不够准确。
我更喜欢这样一句话:
Coroutine
是一套任务管理模型。
其中:
CoroutineContext:
运行环境。
Job:
生命周期。
Dispatcher:
线程调度。
Scope:
协程归属。
Suspend:
暂停恢复。
而另一边:
Flow:
连续数据。
StateFlow:
状态。
SharedFlow:
事件。
Channel:
消息。
callbackFlow:
回调接入。
十二、整个系列最大的发现
写到这里,我突然发现:
整个《Kotlin 协程设计思想》系列,其实一直在回答同一个问题:
程序里的任务和数据,
到底应该如何流动?
Google 给出的答案是:
任务:
CoroutineContext
↓
Job
↓
Dispatcher
↓
Scope
↓
Suspend
管理。
数据:
Flow
↓
StateFlow
↓
SharedFlow
↓
Channel
↓
callbackFlow
管理。
所以很多人说:
Kotlin 协程
是一套异步框架。
我觉得:不够准确。
我更喜欢一句话:
Kotlin 协程,不是为了替代 Thread。
而是为了统一程序中的任务流(Task Flow)和数据流(Data Flow)。
写在最后
这个系列,一开始其实只是项目里的一个小问题。
有一天,我看到这样一段代码:
private val scope =
CoroutineScope(
SupervisorJob() + Dispatchers.Default
)
突然冒出一个疑问:
Job 和 Dispatcher 为什么可以直接相加?
于是一路追下去:
CoroutineContext
↓
Job
↓
Dispatcher
↓
launch
↓
Exception
↓
Scope
↓
Supervisor
↓
Suspend
↓
Flow
最后才发现:
Kotlin 协程真正厉害的地方,从来不是:
launch
async
delay
这些 API。
而是它背后的设计思想。
它试图回答的,其实只有一个问题:
如何让程序里的任务和数据,以一种可控、可组合、可维护的方式流动。
系列完结
《Kotlin 协程设计思想》
(一)CoroutineContext 到底是什么?
(二)Job 到底是什么?
(三)Dispatcher 到底是什么?
(四)launch、async、withContext 到底有什么区别?
(五)协程异常为什么这么难理解?
(六)结构化并发到底是什么?
(七)为什么 Kotlin 要设计 Supervisor?
(八)suspend 到底是什么?
(九)Flow 到底是什么?为什么 suspend 还需要 Flow?
(十)Kotlin 协程到底解决了什么问题?
收尾
回头看整个系列,我突然发现:
它其实被自然地分成了两个世界。
Task Flow(任务流)
CoroutineContext
Job
Dispatcher
Scope
Suspend
负责管理:
任务如何创建、调度、暂停、恢复和结束。
Data Flow(数据流)
Flow
StateFlow
SharedFlow
Channel
callbackFlow
负责管理:
数据如何产生、传递、保存和消费。
所以,整个 Kotlin 协程体系,并不是一堆零散 API。
它真正想做的,是统一程序中的任务流和数据流。
而这,也是我写完这个系列以后,对 Kotlin 协程最大的理解。
系列完。