协程基础概念:suspend、构建器与挂起恢复
说实话,协程刚出来那会儿,我看源码也是一脸懵。什么挂起、恢复、suspend关键字,感觉像魔法一样。但后来调了几次泄漏问题,才真正理解这些基础概念有多重要。
嗯,这一章我们就聊聊协程最核心的三个东西:suspend函数、协程构建器(launch/async),以及挂起与恢复机制。把这些搞懂了,后面排查泄漏才能有的放矢。
1. suspend函数:协程的基本操作单元
suspend函数,说白了就是给编译器一个信号:这个函数可能会暂停执行。我在项目中遇到过不少同事,以为suspend就是“阻塞线程”,其实完全不是一回事。
看个最简单的例子:
suspend fun fetchData(): String {
// 模拟网络请求
delay(1000L) // 挂起1秒,但不阻塞线程
return "数据加载完成"
}
关键点在于:
- suspend函数只能在协程内部或其他suspend函数中调用。普通函数里调不了,编译器会直接报错。
- 它不会阻塞当前线程。线程在协程挂起期间可以去做别的事,比如处理其他协程。
- 本质上是一个回调的语法糖。编译器会把suspend函数转换成状态机,每次挂起就是一个状态点。
2. 协程构建器:launch与async
协程构建器就是启动协程的入口。我早期用Kotlin协程时,经常搞混这两个家伙,结果出了几次泄漏,不得不重构代码。
| 构建器 | 返回值 | 用途 | 异常处理 |
|---|---|---|---|
| launch | Job | 启动一个不返回结果的协程(类似线程的start) | 异常默认传递给父协程 |
| async | Deferred<T> | 启动一个返回结果的协程(类似Callable) | 异常在调用await()时抛出 |
launch 是最常用的。你只是想“干一件事”,不关心结果,就用它。比如更新UI、写日志。
async 适合并发执行任务,然后统一汇总结果。但要注意,async不是单纯的并行,它只是启动了协程,调用await()才挂起等待结果。
// launch 示例:发起一个网络请求,完成后更新UI
scope.launch {
val result = fetchData()
updateUI(result) // 这里不需要返回值
}
// async 示例:并发请求两个接口,汇总数据
scope.launch {
val deferred1 = async { fetchUserInfo() }
val deferred2 = async { fetchUserPosts() }
val user = deferred1.await()
val posts = deferred2.await()
showProfile(user, posts)
}
3. 挂起与恢复机制:协程的“断点续传”
挂起和恢复,是协程最神奇的地方。你想想看,一个函数执行到一半,停下来去做别的事,过一会儿再回来接着干——这怎么实现的?
其实原理不复杂:编译器把suspend函数改写成一个状态机。每个挂起点都对应一个状态。协程挂起时,把当前的状态和局部变量保存下来;恢复时,根据状态跳到对应的位置继续执行。
举个生活中的例子:你在餐厅点餐。传统线程模型是你站在柜台前一直等着(阻塞线程)。协程模型是你先点餐,拿个号,然后回座位玩手机(挂起)。等餐好了,广播叫你的号,你再去取餐(恢复)。
关键流程:
- 调用suspend函数:编译器检查是否有挂起点。
- 遇到挂起点(比如delay、网络请求):保存当前上下文(Continuation对象)到堆栈。
- 让出线程:线程返回执行其他协程或任务。
- 条件满足后(比如延迟结束、数据返回):调度器把协程重新加入执行队列。
- 恢复执行:从保存的挂起点继续往下走。
4. 协程的生命周期:Job和Deferred
每次启动一个协程,都返回一个Job或Deferred对象。它们代表了协程的生命周期。我排查泄漏时,第一个查的就是这些对象有没有被正确取消。
// Job 的状态流转
val job: Job = scope.launch {
// ...
}
job.cancel() // 取消协程
job.join() // 等待协程结束
// Deferred 是 Job 的子接口,多了 await()
val deferred: Deferred<String> = scope.async {
"结果"
}
val result = deferred.await()
Job的状态包括:New、Active、Completing、Completed、Cancelling、Cancelled。搞清楚这些状态,就能知道协程到底泄漏在哪一步。
5. 概念关系图
下面这张图,把这一章的核心知识点串了一遍。你看一遍,再回头看书,会清晰很多。
嗯,suspend函数、协程构建器、挂起恢复机制——这三个点就像协程的三根支柱。你理解得越深,排查泄漏时就越能一眼看出问题在哪。
我记得第一次接触协程时,觉得挂起恢复特别玄乎。后来自己画了张状态机图,才恍然大悟:其实就是编译器帮你写了个switch-case。每个挂起点就是一个case,你用上了没有挂起的调用,就跳出这个状态机,那协程当然就乱掉了。