一個協程的生命週期是這樣的,
±---------+ ±---------------------+
| START |----------------------->| SUSPENDED |
±---------+ ±---------------------+
| ^
V |
±-----------+ completion invoked ±----------------+
|RUNNING|------------------------->| COMPLETED |
±-----------+ ±--------------------+
協程的重點是可以在掛起和運行兩個狀態中切換。實現這個能力的關鍵在於協程實現了continuation接口。
可重入性
之前的分析裏說過continuation接口,這篇着重分析它的設計邏輯,
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
resumeWith是協程的重點,每次切出去到suspend狀態,再進入running狀態都是通過resumeWith接口。我們所寫的每一個coroutine,都會continuation接口。
有意思的是,它是什麼時候實現的,怎麼實現的?
在launch{}的源碼裏可以看到有個block參數,這個block就是我們所寫的協程代碼,
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
}
block具體是個什麼東西,得看編譯後的字節碼。從協程源碼裏猜測,它肯定是一個實現了continuation的類,因此它纔能有可重入性。
把編譯後的字節碼用jd-gui打開可以看到,我們所寫的協程會給編譯器插入代碼,實現SuspendLambda接類,
在編譯後的字節碼裏見不到resumeWith()函數,說明這個函數必定是在SuspendLambda中有實現。
而SuspendLabmda是在標準庫裏的,ContinuationImpl.kt文件下,
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
constructor(arity: Int) : this(arity, null)
public override fun toString(): String =
if (completion == null)
Reflection.renderLambdaToString(this) // this is lambda
else
super.toString() // this is continuation
}
如果從這裏往上追進去,會在 BaseContinuationImpl 類下面發現 resumeWith()接口。
internal abstract class BaseContinuationImpl(
// This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
// it has a public getter (since even untrusted code is allowed to inspect its call stack).
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param) //調用class裏的invokeSuspend
最後一行會調用 invokeSuspend。這個調用鏈可以看出我們所寫的協程的可重入性是怎麼實現的了。
從block到coroutine
上面的分析,只展現了一個block目前所具有的特點。雖然它具有了可重入性,但它還沒有可被攔截的能力,也就是Intercept。什麼時候變成可以攔截呢。在Cancellable.kt裏,可以看到,
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
createCoroutineUnintercepted創建了一個coroutine。顧名思義,這時候是一個未攔截的coroutine。這個函數又再次是 kotlin 標準庫裏的。它在 IntrinsicsNative.kt 中,
@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(receiver, probeCompletion) //調用create函數
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
現在它會去調用create函數。而create函數又在哪呢?在編譯後的字節碼裏。
再一次,kotlin編譯器在編譯過程幫我們插入了這段代碼。從設計上來看,create其實是做了一次封裝,把需要的對象通過參數傳進去。
現在的block距離真正意義上的coroutine,還差一個可派發性。雖然它已經具有了可重入,可攔截,還差一點。
攔截器-Interceptor
block的可派發性是在 Cancellable.kt 的 intercept() 函數賦予的。上面創建完的block,是一個繼承了SuspendLambda的block,而繼承樹上和intercept()有關的是ContinuationImpl,
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)
public override val context: CoroutineContext
get() = _context!!
@Transient
private var intercepted: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
最後這裏,是真正給block賦予可派發性的地方。context[ContinuationInterceptor]獲取的是最開始給上下文設定的Dispatcher,不管是 DefaultScheduler,還是EventLoop,他們都有個公有的父類CoroutineDispatcher。而interceptContinuation的唯一實現就在這個類裏。
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
這裏可以看到它做了一次代理模式,把Dispatcher封裝了進去,作爲 DispatchedContinuation的一個成員。
至此一個block就完成了它的整個創建過程。從一個block,到支持可重入,到支持可攔截,最後支持可派發。
所以會看到雖然協程的外部概念很清晰,只是一個 coroutine,但在協程內部,實際上支撐它的還有Continuation,Dispatch,Intercept。還是挺複雜的。