一、挂起函数源码分析
1.编译器转换(CPS 转换)
suspend fun fetchData(url: String): String {
delay(1000) // 挂起点1
val result = api.request(url) // 挂起点2
return result
}
//编译器会将其转换为带有 Continuation 参数的普通函数,函数签名变为:
fun fetchData(url: String, continuation: Continuation<String>): Any?
- 新增的
Continuation<String>
参数:本质是一个 “回调对象”,保存了挂起函数暂停时的执行状态(如局部变量、程序计数器、调用栈信息等),用于后续恢复执行。 - 参数返回值变为
Any?
:有两种可能结果:- 若函数正常执行完成(未挂起),返回实际结果(如
String
); - 若函数需要挂起,返回一个特殊标记
COROUTINE_SUSPENDED
,告知调用者 “当前函数已挂起,需等待后续恢复”。
- 若函数正常执行完成(未挂起),返回实际结果(如
Continuation
是挂起函数状态保存与恢复的核心接口,源码定义简化如下:
interface Continuation<in T> {
val context: CoroutineContext // 协程上下文(含调度器等信息)
fun resumeWith(result: Result<T>) // 恢复执行的触发方法
}
context
:保存协程的调度器(Dispatcher)、Job 等信息,决定恢复执行时使用哪个线程。resumeWith
:当挂起条件满足(如delay
时间到、网络请求返回)时,通过此方法触发挂起函数的恢复,result
为挂起期间的执行结果(成功或异常)。
2.状态机实现
State
),每个状态对应一个挂起点前后的逻辑:
private enum class FetchDataState {
INITIAL, // 初始状态(未执行)
AFTER_DELAY, // 执行完delay后的状态
AFTER_REQUEST // 执行完api.request后的状态
}
同时,函数会被改造为一个状态机处理函数,大致逻辑如下:
fun fetchData(url: String, continuation: Continuation<String>): Any? {
// 从continuation中获取或初始化状态机
val stateMachine = continuation as? FetchDataStateMachine ?: FetchDataStateMachine(continuation)
val context = continuation.context
return when (stateMachine.state) {
FetchDataState.INITIAL -> {
// 第一次执行:处理初始逻辑,调用第一个挂起函数(delay)
stateMachine.url = url // 保存局部变量
stateMachine.state = FetchDataState.AFTER_DELAY // 更新状态
// 调用delay,将当前状态机作为其continuation传递
val delayResult = delay(1000, stateMachine)
if (delayResult == COROUTINE_SUSPENDED) {
return COROUTINE_SUSPENDED // 触发挂起
} else {
// 若delay未挂起(如时间为0),直接进入下一个状态
stateMachine.state = FetchDataState.AFTER_DELAY
// 继续执行下一段逻辑(相当于“伪恢复”)
fetchData(url, stateMachine)
}
}
FetchDataState.AFTER_DELAY -> {
// 从delay恢复后:执行第二个挂起函数(api.request)
stateMachine.state = FetchDataState.AFTER_REQUEST
val requestResult = api.request(stateMachine.url, stateMachine) // 传递状态机
if (requestResult == COROUTINE_SUSPENDED) {
return COROUTINE_SUSPENDED // 再次挂起
} else {
// 若request未挂起,直接进入下一个状态
stateMachine.state = FetchDataState.AFTER_REQUEST
fetchData(url, stateMachine)
}
}
FetchDataState.AFTER_REQUEST -> {
// 从api.request恢复后:返回结果
val result = stateMachine.result // 从状态机中取出request的结果
stateMachine.continuation.resumeWith(Result.success(result)) // 通知上层恢复
null // 执行结束
}
}
}
// 状态机类:保存局部变量和当前状态
private class FetchDataStateMachine(
val continuation: Continuation<String>
) : Continuation<String> by continuation {
var state: FetchDataState = FetchDataState.INITIAL
var url: String = "" // 保存局部变量url
var result: String = "" // 保存api.request的结果
}
3.挂起函数触发后的挂起和恢复流程
- 编译转换:挂起函数被转换为带
Continuation
参数的函数,逻辑拆分为状态机。 - 初始执行:从
INITIAL
状态开始,执行到第一个挂起点(如delay
)。 - 挂起触发:若挂起函数返回
COROUTINE_SUSPENDED
,当前函数返回该标记,线程释放。 - 等待恢复:挂起期间,
Continuation
(状态机)被保存,等待条件满足(如时间到)。 - 恢复执行:条件满足后,
Continuation.resumeWith
被调用,状态机从上次保存的状态(如AFTER_DELAY
)继续执行。 - 执行完成:当所有状态执行完毕,通过最上层的
Continuation
返回结果,整个挂起函数执行结束。
阶段总结
挂起函数因带有suspend
关键字会被 Kotlin 编译器进行 CPS 转换,即转换为带有Continuation
参数的普通函数,Continuation
作为回调对象保存挂起时的执行状态(如局部变量、调用栈等),函数返回值变为Any?
,要么是实际结果,要么是特殊标记COROUTINE_SUSPENDED
表示挂起;
转换后的函数会形成由switch
和label
组成的状态机结构,编译器生成对应挂起点的状态枚举,状态机通过when
表达式实现状态流转,状态机类保存局部变量和当前状态,每次状态改变代表挂起函数被调用一次;
其挂起和恢复流程为:编译转换后从INITIAL
状态开始执行,到挂起点时若返回COROUTINE_SUSPENDED
则触发挂起并释放线程,挂起期间Continuation
保存状态等待条件满足,条件满足后通过Continuation.resumeWith
恢复执行,状态机从上次保存的状态继续,直至所有状态执行完毕,通过最上层Continuation
返回结果。
二、lauch的启动源码分析
底层都依赖着协程构建器(Coroutine Builder)和协程上下文(CoroutineContext)
1.协程启动的入口函数:CoroutionScope.launch
launch
是CoroutineScope
的扩展函数,用于启动一个不返回结果的协程。其源码定义如下:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
// 获取当前协程作用域的上下文
val newContext = newCoroutineContext(context)
// 创建一个新的协程实例(根据不同的启动模式)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block)
else
StandaloneCoroutine(newContext, active = true)
// 根据启动模式决定是立即执行还是延迟执行
coroutine.start(start, coroutine, block)
return coroutine
}
context: CoroutineContext
协程上下文,包含调度器(Dispatcher)、Job、异常处理器等元素。默认值为EmptyCoroutineContext
,表示使用父协程的上下文。start: CoroutineStart
协程启动模式,枚举类型,主要包括:DEFAULT
:立即调度协程执行LAZY
:懒启动,需显式调用start()
或join()
触发ATOMIC
:立即执行,但在调度前无法取消UNDISPATCHED
:立即在当前线程执行,直到第一个挂起点
block: suspend CoroutineScope.() -> Unit
协程体,是一个挂起函数,可在其中调用其他挂起函数。
2.协程上下文的构建与继承
newCoroutineContext(context)
是关键方法,用于合并当前作用域的上下文和用户传入的上下文:
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
// 获取当前作用域的上下文(如GlobalScope、lifecycleScope的上下文)
val combined = this.coroutineContext + context
// 如果没有显式指定Job,则添加一个父Job(确保结构化并发)
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined[Job] != null) debug else debug + Job()
}
关键点:
- 上下文合并:通过
+
操作符合并上下文元素,相同类型的元素后添加的会覆盖前面的(如重复指定 Dispatcher)。 - 默认 Job:若用户未显式指定 Job,会自动添加一个,确保协程有父子关系,实现结构化并发。
3.协程示例的创建与启动模式
根据start
参数的不同,会创建不同类型的协程实例:
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block)
else
StandaloneCoroutine(newContext, active = true)
两种协程实现:
LazyStandaloneCoroutine
用于LAZY
模式,协程体不会立即执行,直到调用start()
或join()
。StandaloneCoroutine
用于其他模式,协程体会立即调度执行。
4.协程的真正启动:coroutine.start()
start()
方法是协程执行的核心入口,源码如下:
public fun <R, T> CoroutineStart.invoke(coroutine: R, block: suspend R.() -> T) {
when (this) {
DEFAULT -> coroutine.startCoroutineCancellable(block, coroutine)
ATOMIC -> coroutine.startCoroutine(block, coroutine)
UNDISPATCHED -> coroutine.startCoroutineUndispatched(block, coroutine)
LAZY -> Unit // 懒启动模式下不执行,等待用户显式调用start()
}
}
不同启动模式的差异:
DEFAULT
模式
调用startCoroutineCancellable()
,允许在调度前取消协程:public fun <T> (suspend () -> T).startCoroutineCancellable( completion: Continuation<T> ) { createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit)) }
ATOMIC
模式
调用startCoroutine()
,不允许在调度前取消:public actual fun <T> (suspend () -> T).startCoroutine( completion: Continuation<T> ) { createCoroutineUnintercepted(completion).intercepted().resumeWith(Result.success(Unit)) }
UNDISPATCHED
模式
调用startCoroutineUndispatched()
,直接在当前线程执行直到第一个挂起点:internal fun <R, T> (suspend R.() -> T).startCoroutineUndispatched( receiver: R, completion: Continuation<T> ) { val continuation = createCoroutineUnintercepted(receiver, completion) val context = continuation.context val uCont = continuation.intercepted() if (context[ContinuationInterceptor] == Dispatchers.Unconfined) { // 无限制调度器:直接在当前线程执行 uCont.resumeWith(Result.success(Unit)) } else { // 其他调度器:先切换到指定线程再执行 with(context) { uCont.resumeWith(Result.success(Unit)) } } }
5.async与launch的源码核心差异
async
与launch
的源码结构相似,但有两个关键区别:
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
// 创建一个携带结果的协程(继承自Job,但多了结果缓存和await()方法)
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block)
else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
差异点:
返回值类型
launch
返回Job
(只能控制生命周期,无法获取结果)async
返回Deferred<T>
(继承自Job
,但可通过await()
获取结果)
结果缓存
DeferredCoroutine
内部维护了一个Result<T>
缓存,确保await()
可重复获取结果,且支持多协程并发等待。
阶段总结
Kotlin 协程的启动依赖于协程构建器和协程上下文,其中CoroutineScope.launch
作为入口函数,通过合并当前作用域上下文与用户传入的上下文创建新的协程上下文,若未显式指定 Job 则自动添加以确保结构化并发;
根据启动模式(如 DEFAULT 立即调度、LAZY 懒启动、ATOMIC 立即执行但调度前不可取消、UNDISPATCHED 立即在当前线程执行至首个挂起点)创建不同的协程实例(如 LazyStandaloneCoroutine 或 StandaloneCoroutine),并通过coroutine.start()
触发协程执行,不同启动模式对应不同的执行策略;
async
与launch
源码结构相似,但async
返回继承自 Job 的 Deferred<T>类型,可通过 await () 获取结果,而launch
仅返回 Job 用于控制生命周期。