Kotlin 協程源碼閱讀筆記 —— 協程工作原理
Kotlin
協程在網上有很多的人把它吹得神乎其神,什么性能多么多么好,效率比線程高多少多少,balabala 一堆優點。首先在我看來協程和線程壓根兒就沒有可比性,就好像說姚明和劉翔誰更厲害一樣,線程是操作系統的調度的基本單位,線程也是 CPU
執行的一個基本任務;而協程只是在編程語言上定義的一種優化多線程通信、調度的一種編程方式(至少 Kotlin
中是這樣),而操作系統可不認識什么是協程,而協程中的任務最終也是在線程上執行。
在 Kotlin
協程上來說它的最大的優點只有一個它能夠以同步的方式來寫異步代碼,能夠干掉編程中的地獄回調(通過類似于 RxJava
流的編程方式也能夠干掉地獄回調,不過不是本篇文章中的討論內容),而它的其他優點也都是這一個優點的發散,不要小瞧這個優點,如果消除了各種異步 Callback
,能夠在很大的程度上提高代碼的可閱讀性,減少 BUG
的產生,也更容易能夠定位到 BUG
。
簡單了協程的優點,后續就要看看它是怎么工作的了,那么準備就緒后就開啟今天的內容。
用 Callback 寫異步任務
假如我有以下的異步任務:
val delayExecutor: ScheduledThreadPoolExecutor by lazy {
ScheduledThreadPoolExecutor(1)
}
fun delay(time: Long, callback: () -> Unit) {
delayExecutor.schedule({ callback() }, time.coerceAtLeast(0), TimeUnit.MILLISECONDS)
}
我用 delay()
來實現一個異步耗時任務,實現就是通過 delayExecutor
添加一個定時任務,任務執行時就調用 callback
。
我有以下代碼要調用異步任務:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
helloWorld {
println(it)
}
}
fun helloWorld(callback: (s: String) -> Unit) {
hello { hello ->
world { world ->
callback("${hello}${world}")
}
}
}
fun hello(callback: (s: String) -> Unit) {
delay(500) {
callback("Hello, ")
}
}
fun world(callback: (s: String) -> Unit) {
delay(500) {
callback("World!!")
}
}
}
我在 onCreate()
函數中調用了異步任務 helloWorld()
,成功后會在 callback
中打印最后的結果。 helloWorld()
又由 hello()
與 world()
兩個任務組成,hello()
任務成功后在調用 world()
任務,最后結合 hello()
與 world()
兩個任務的結果的結果回調 helloWorld()
的 callback
。
用協程寫異步任務
同樣是上面的異步任務,我用 Kotlin
協程改造一下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val coroutineScope = CoroutineScope(Dispatchers.Default)
coroutineScope.launch {
println(helloWorld())
}
}
suspend fun helloWorld(): String {
val hello = hello()
val world = world()
return hello + world
}
suspend fun hello(): String {
return delaSuspend(500, "Hello, ")
}
suspend fun world(): String {
return delaSuspend(500, "World!!")
}
suspend fun <T> delaSuspend(time: Long, data: T): T {
return suspendCancellableCoroutine { cont ->
delay(time) {
cont.resumeWith(Result.success(data))
}
}
}
}
首先我把上面的 delay()
耗時任務的回調改造成了協程 suspend
方法,具體的改造參考 delaSuspend()
方法的實現。然后 hello()
與 world()
也都是 suspend()
方法,他們都是通過調用 delaSuspend()
來模擬異步任務,在 helloWrold()
中分別調用 hello()
和 world()
方法,這里都是以同步的方式調用的哦,然后組合他們的結果然后返回。在 onCreate()
新建一個協程,然后也是直接以同步的方式調用 helloWorld()
。
和改造前的代碼有一個非常顯著的特點就是消滅的所有的 callback
,所有的異步任務都是以同步的方式調用的,你可能也會吐槽也沒感覺比之前優化了多少,上面 demo
中的任務比較簡單,總共才 3 個 callback
,而且也沒有處理異常的回調,callback
的層級最大也才 2。越復雜的任務協程的優勢就會越大。
Kotlin 協程工作原理
雖然在源碼中我們看到的協程代碼是同步的,其實虛擬機執行的時候它還是一個不折不扣的 callback
,這主要歸功于 Kotlin
編譯器的處理,我們就以上面的 demo
來一步一步分析它的工作方式。
CoroutineScope
如果要開啟一個協程就需要先通過 CoroutineScope()
方法創建一個 CoroutineSope
,在這個方法中可以指定我們的默認 CoroutineContext
。
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
CoroutineScope
的接口非常簡單,只需要實現一個 CoroutineContext
。
我們來看看 CoroutineScope()
方法的實現:
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = context
// CoroutineScope is used intentionally for user-friendly representation
override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
上面的代碼非常簡單就只是把我們添加的 CoroutineContext
設置到 CoroutineScope
,這里注意添加了一個 Job
的 CoroutineContext
,每個協程啟動時都會創建一個 Job
對象,這些由 CoroutineScope
啟動的協程的 Job
都是 CoroutineScope
中的 Job
的子任務,而協程里面還可以再啟動子協程,這個子協程的 Job
的父 Job
就是啟動他的協程的 Job
。所以通過 Job
就構成了一個任務的繼承鏈。當父 Job
取消后他的子 Job
也會被取消。所以如果是 CoroutineScope
中的頂級父 Job
取消了,那么用他啟動的所有的協程或者孫協程等等也都會被取消。CoroutineScope#cancel()
的方法實現就是通過調用 CoroutineContext
中的 Job
的 cancel()
方法實現的:
public fun CoroutineScope.cancel(cause: CancellationException? = null) {
val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
job.cancel(cause)
}
通常我們為了防止內存泄漏,在 Activity
或者 Fragment
或者一些什么別的組件退出后都會調用他們所對應的 CoroutineScope#cancel()
方法,避免內存泄漏。
啟動一個協程
我們啟動協程是通過 CoroutineScepe#launch()
擴展函數來完成的,其中的 Lambda
對象就是協程執行開始的第一個方法,我們來看看它的源碼實現:
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
}
這里會對傳入的 CoroutineContext
進行修改,處理的方法是 newCoroutineContext()
;然后根據啟動類型創建一個 coroutine
對象,默認的實現是 StandaloneCoroutine
類,然后調用 Coroutine#start()
方法,最后返回 Coroutine
。這里要非常注意 StandaloneCoroutine
是一個 Job
,Continuation
(很多人中文翻譯它為續體,后續會重點講),CoroutineScope
(也就是他也能夠啟動協程,也就是當前協程的子協程)。后面我們會再看這些對象所處理的邏輯,現在有點懵也沒關系。
我們繼續看看 newCoroutineContext()
方法對傳入的 CoroutineContext
的處理:
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = foldCopies(coroutineContext, context, true)
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
這里會添加一個 CoroutineId
的 CoroutineContext
用來記錄 Coroutine
的名字,其實就是修改協程運行時的線程的名字,添加上協程編號的信息;這里還會判斷是否有 CoroutineInterceptor
的 CoroutineContext
,如果沒有,使用 Dispatcher.Default
作為默認的 CoroutineInterceptor
。
然后簡單看看 StandaloneCoroutine
的實現:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
它是繼承與 AbstractCoroutine
,它重寫了 handleJobException()
方法來處理協程的異常,處理調用的方法是 handleCoroutineException()
,我們來看看它的實現:
@InternalCoroutinesApi
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// Invoke an exception handler from the context if present
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleUncaughtCoroutineException(context, handlerException(exception, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
handleUncaughtCoroutineException(context, exception)
}
這里會查找 CoroutineContext
中是否有 CoroutineExceptionHandler
,如果有異常就交給它來處理,如果沒有就由 Global
的 Handler
來處理,默認就是崩潰啦。
然后就是調用 Coroutine#start()
方法來啟動一個協程了,看看它的代碼:
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
start(block, receiver, this)
}
@InternalCoroutinesApi
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
上面的 block
就是我們啟動協程時傳遞過來的 Lambda
對象,然后 receiver
和 completion
都是我們上面創建的 StandaloneCoroutine
對象。繼續看看 startCoroutineCancellable()
方法的實現:
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
}
這里首先通過 createCoroutineUnintercepted()
方法將我們的 Lambda
對象構建成一個 Continuation
對象;然后調用 intercepted()
方法對原來的 Continuation
對象進行攔截器的處理;然后調用 resumeCancellableWith()
方法來觸發協程的開始。上面的三個方法都非常重要。
這里在開始之前非常有必要解釋一下 Continuation
,很多人中文翻譯成續體,我就不用中文的名字了,還是繼續用英文來表示。前面我們說到協程的本質其實就是一個 Callback
,而用來控制 Callback
回調成功/失敗就是 Continuation
很重要的一個職責,同時它還記錄了當前方法對應的執行的位置(像程序計數器一樣),上次執行后的中間結果等等。Continuation
還會涉及到兩個非常重要的概念那就是 suspend
和 resume
,中文翻譯成掛起和恢復,我還是使用英文名詞來表示他們。所謂的 suspend
其實就相當于我們調用了一個異步方法后等待 callback
時的協程狀態,這時由于 callback
還沒有回來當前的線程還可以做其他的任務;而 resume
就是 callback
回調成功需要喚醒原來的協程繼續執行。這也就是很多人說得很邪乎的掛起與恢復,說白了就是調用異步任務時就掛起,callback
成功或者失敗就是恢復,后面我們還會從源碼中看到他具體是怎么掛起和恢復的(其實上面的 resumeCancellableWith()
方法就算是恢復)。
createCoroutineUnintercepted()
這個方法其實就是對原來 launch()
方法傳遞過來的 Lambda
方法進行改造,我們直接看看,反編譯后的原來的 Lambda
對象:
其中我們發現它繼承于 SuspendLambda
對象,而 SuspendLambda
繼承于 ContinuationImpl
對象這個對象非常重要,是 Continuation
的一個實現。而當前的 Continuation
執行時的入口函數就是 invokeSuspend()
方法。后面我們會看到調用這個方法的邏輯。而它的構造函數中還有一個 Continuation
對象,這個其實就是上面的 StandalongCoroutine
對象,這個對象可以理解為父級的 Continuation
對象,在 Kotlin
源碼中通常被稱為 completion
,這個就是表示當前 Continuation
執行完成后需要通知父級的 Continuation
繼續執行。
intercepted()
我們繼續看看 intercepted()
方法對我們原來的 Continuation
做了什么處理:
@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? ContinuationImpl)?.intercepted() ?: this
調用了 ContinuationImpl
的 intercepted()
方法,我們繼續追蹤:
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
繼續調用 ContinuationIntercetor#interceptContinuation()
方法,我們之前設置的 CoroutineInterceptor
是 Dispatchers.Default
。 我們來看看 CoroutineDispatcher#interceptContinuation()
方法的實現:
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
將原來的 Dispather
和 Continuation
作為參數構建了一個新的 DispatchedContinuation
對象。
resumeCancellableWith()
上面我們也講到它就相當于 resume
協程,他也是我們第一次 resume
協程。我們來看看它的實現:
@InternalCoroutinesApi
public fun <T> Continuation<T>.resumeCancellableWith(
result: Result<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
else -> resumeWith(result)
}
由于我們在 intercetped()
方法中將原來的 Continuation
對象,轉換成了 DispatchedContinuation
對象,所以我們這里調用的是 DispatchedContinuation#resumeCancellableWith()
方法,我們看看它的實現:
@Suppress("NOTHING_TO_INLINE")
internal inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun resumeUndispatchedWith(result: Result<T>) {
withContinuationContext(continuation, countOrElement) {
continuation.resumeWith(result)
}
}
如果 Dispatcher#isDispatchNeeded()
返回 true
就表示可以使用 Dispatcher
來處理任務,也就是可以做到后續的任務最終在 Dispatcher
中的線程池中執行,通過 Dispatcher#dispatch()
方法下發任務,最后執行時是執行 run()
方法,實現是 DispatchedTask#run()
方法。反之就直接在當前線程調用 Continuation#resultWith()
方法。看看 DispatchedTask#run()
方法的實現:
final override fun run() {
assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatching
val taskContext = this.taskContext
var fatalException: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
withContinuationContext(continuation, delegate.countOrElement) {
// ...
if (job != null && !job.isActive) {
// ...
} else {
if (exception != null) {
continuation.resumeWithException(exception)
} else {
continuation.resume(getSuccessfulResult(state))
}
}
}
} catch (e: Throwable) {
// ...
} finally {
// ...
}
}
在 run()
方法執行過程中會判斷協程是否報錯了,如果沒有報錯直接執行 Continuation#result()
方法,如果有錯調用 Continuation#resumeWithException()
方法,上面說到這個 Continuation
在這里的實現類是 Lambada
對象繼承 SuspendLambda
對象實現的,這個 resume()
方法最終是由 BaseContinuationImpl#resumeWith()
方法實現的,這個方法可以說是 Kotlin
協程的靈魂,我們來看看它的實現,注意理解:
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
// 通知 debug 協程已經 resume
probeCoroutineResumed(current)
with(current) {
// 父級的 continuation
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
// 調用 invokeSuspend 入口函數
val outcome = invokeSuspend(param)
// 如果返回 COROUTINE_SUSPENDED 就表示掛起,同時退出循環
if (outcome === COROUTINE_SUSPENDED) return
// 如果返回不是 COROUTINE_SUSPENDED 就表示該 Continuation 方法已經執行完成了,需要通知它的父級的 Continuation,然后父級的 Continuation 繼續執行。
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
// 進入下次循環,調用父級的 Continuation 的 invokeSuspend() 方法
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
// 恢復頂級的 Continuation,我們的代碼中是 StandaloneCoroutine
completion.resumeWith(outcome)
return
}
}
}
}
該 Continuation
中每調用一次 Kotlin
中的 suspend
的方法,Continuation
都會調用一次它的 invokeSuspend()
方法,當然每次調用 invokeSuspend()
方法執行的代碼都不一樣,Continuation
會通過一個 label
來記錄 invokedSuspend()
執行的位置,后面我們會看到這部分代碼,簡單再描述一下上面的代碼:
- 通過
probeCoroutineResumed()
方法通知協程進入resume
狀態。 - 調用
invokeSuspend()
方法進入協程方法的具體執行,然后判斷返回值,如果返回值是COROUTINE_SUSPENDED
就表示當前協程需要suspend
(也就是在執行一個異步任務,等待后續異步任務成功后再調用resumeWith()
方法resume
。);如果返回值不是COROUTINE_SUSPENDED
就表示當前的Continuation
已經執行完成了。 - 當前
Continuation
執行完成了后,就需要在下次循環中調用父級的Continuation
的invokeSuspend()
方法,直到頂級的Continuation
執行完,在我們這里是StandaloneCoroutine
,注意理解這個循環調用。
我們站在初次啟動協程的邏輯來看看 resumeWith()
這個方法,初次調用 resumeWith()
時,對應的 Continuation
就是 SuspendContinuation
,而最終的實現是由我們 launch
時傳遞進去的 Lambda
對象生成的,而父級的 Continuation
就是 StandaloneCoroutine
,SuspendContinuation
執行完成后就會調用 StandaloneCoroutine
的 resumeWith()
方法,這也就標志當前的協程已經執行完成。
由于在 Kotlin
協程相關代碼編譯過程中會生成多個類似于 Lambda
對象那樣的匿名對象,不利于我們后續的源碼分析,我把 demo
中的編譯后的代碼反編譯后再重新整理命名,方便后續的代碼的分析。
MainActivity
public final class MainActivity extends AppCompatActivity {
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(Dispatchers.getDefault()), null, null, new LaunchContinuation(this, null), 3, null);
}
public final java.lang.Object helloWorld(kotlin.coroutines.Continuation<? super java.lang.String> continuation) {
HelloWorldContinuation helloWorldContinuation = null;
if (!(continuation instanceof HelloWorldContinuation)) {
helloWorldContinuation = new HelloWorldContinuation(this, continuation);
} else {
helloWorldContinuation = (HelloWorldContinuation) continuation;
}
Object suspend = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (helloWorldContinuation.label) {
case 0: {
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
StringBuilder stringBuilder = new StringBuilder();
helloWorldContinuation.param1 = this;
helloWorldContinuation.param2 = stringBuilder;
helloWorldContinuation.label = 1;
Object result = hello(helloWorldContinuation);
if (result == suspend) {
return suspend;
} else {
// 我們的代碼中不會有這種情況
}
}
case 1: {
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
MainActivity mainActivity = (MainActivity) helloWorldContinuation.param1;
StringBuilder stringBuilder = (StringBuilder) helloWorldContinuation.param2;
String lastResult = (String) helloWorldContinuation.result;
stringBuilder.append(lastResult);
helloWorldContinuation.param1 = stringBuilder;
helloWorldContinuation.param2 = null;
helloWorldContinuation.label = 2;
Object result = world(helloWorldContinuation);
if (result == suspend) {
return suspend;
} else {
// 我們的代碼中不會有這種情況
}
}
case 2: {
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
StringBuilder stringBuilder = (StringBuilder) helloWorldContinuation.param1;
String lastResult = (String) helloWorldContinuation.result;
stringBuilder.append(lastResult);
return stringBuilder.toString();
}
default: {
}
}
throw new UnsupportedOperationException("Method not decompiled: com.tans.coroutine_test.MainActivity.helloWorld(kotlin.coroutines.Continuation):java.lang.Object");
}
public final Object hello(Continuation<? super String> continuation) {
return delaSuspend(500L, "Hello, ", continuation);
}
public final Object world(Continuation<? super String> continuation) {
return delaSuspend(500L, "World!!", continuation);
}
public final <T> Object delaSuspend(long time, T t, Continuation<? super T> continuation) {
CancellableContinuationImpl cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(continuation), 1);
cancellable$iv.initCancellability();
DelayKt.delay(time, new MainActivity$delaSuspend$2$1(cancellable$iv, t));
Object result = cancellable$iv.getResult();
if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended(continuation);
}
return result;
}
}
LaunchContinuation
LaunchContinuation
就是由 launch()
方法的 Lambda
對象生成的 Continuation
對象,它本來是一個無規則的對象名字,為了代碼好閱讀我把它的名字修改成了 LaunchContinuation
:
final class LaunchContinuation extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
int label;
final MainActivity mainActivity;
public LaunchContinuation(MainActivity mainActivity, Continuation<? super LaunchContinuation> continuation) {
super(2, continuation);
this.mainActivity = mainActivity;
}
@Override
public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
return new LaunchContinuation(this.mainActivity, continuation);
}
public final Object invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) {
return ((LaunchContinuation) create(coroutineScope, continuation)).invokeSuspend(Unit.INSTANCE);
}
@Override
public final Object invokeSuspend(Object $result) {
Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
this.label = 1;
Object helloWorld = this.mainActivity.helloWorld(this);
if (helloWorld != coroutine_suspended) {
$result = helloWorld;
break;
} else {
return coroutine_suspended;
}
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
System.out.println($result);
return Unit.INSTANCE;
}
}
HelloWorldContinuation
HelloWroldContinuation
是由 helloWorld()
函數生成的一個 Continuation
對象,它本來也是一個匿名對象,為了方便源碼閱讀,我修改了該類的命名。
public final class HelloWorldContinuation extends ContinuationImpl {
public Object param1;
public Object param2;
public int label;
public Object result;
final MainActivity mainActivity;
public HelloWorldContinuation(MainActivity mainActivity, Continuation<? super HelloWorldContinuation> continuation) {
super(continuation);
this.mainActivity = mainActivity;
}
@Override
public final Object invokeSuspend(Object obj) {
this.result = obj;
this.label |= Integer.MIN_VALUE;
return this.mainActivity.helloWorld(this);
}
}
DelayKt
public final class DelayKt {
private static final Lazy delayExecutor$delegate = LazyKt.lazy(DelayKt$delayExecutor$2.INSTANCE);
public static final ScheduledThreadPoolExecutor getDelayExecutor() {
return (ScheduledThreadPoolExecutor) delayExecutor$delegate.getValue();
}
public static final void delay(long time, final Function0<Unit> callback) {
Intrinsics.checkNotNullParameter(callback, "callback");
getDelayExecutor().schedule(new Runnable() { // from class: com.tans.coroutine_test.DelayKt$$ExternalSyntheticLambda0
@Override // java.lang.Runnable
public final void run() {
DelayKt.delay$lambda$0(Function0.this);
}
}, RangesKt.coerceAtLeast(time, 0L), TimeUnit.MILLISECONDS);
}
/* JADX INFO: Access modifiers changed from: private */
public static final void delay$lambda$0(Function0 callback) {
Intrinsics.checkNotNullParameter(callback, "$callback");
callback.invoke();
}
}
-
DelayKt$delayExecutor$2
這是Delay
中Executor
的代理對象
final class DelayKt$delayExecutor$2 extends Lambda implements Function0<ScheduledThreadPoolExecutor> {
public static final DelayKt$delayExecutor$2 INSTANCE = new DelayKt$delayExecutor$2();
DelayKt$delayExecutor$2() {
super(0);
}
@Override
public final ScheduledThreadPoolExecutor invoke() {
return new ScheduledThreadPoolExecutor(1);
}
}
-
MainActivity$delaSuspend$2$1
這是MainActivity
中調用delay()
方法時的Lambda
生成的對象:
final class MainActivity$delaSuspend$2$1 extends Lambda implements Function0<Unit> {
final CancellableContinuation<T> $cont;
final T $data;
public MainActivity$delaSuspend$2$1(CancellableContinuation<? super T> cancellableContinuation, T t) {
super(0);
this.$cont = cancellableContinuation;
this.$data = t;
}
@Override // kotlin.jvm.functions.Function0
/* renamed from: invoke reason: avoid collision after fix types in other method */
public final void invoke2() {
Continuation continuation = this.$cont;
Result.Companion companion = Result.Companion;
continuation.resumeWith(Result.m122constructorimpl(this.$data));
}
}
LaunchContinuation
的執行流程
上一小節講到協程啟動執行開始的方法是調用 launch()
方法中 Lambda
生成的 Continuation
的 resumeWith()
方法,這個 Continuation
我們把它命名成 LaunchContinuation
(實際上是一個和 Lambda
對象一樣的匿名對象,對象名不易閱讀),而 resumeWith()
方法最終會調用 LaunchContinuation#invokeSuspend()
方法(忘記了的同學看看前面分析 BaseContinuationImpl#resumeWith()
代碼的部分),我們來看看 invokeSuspend()
方法的實現:
public final Object invokeSuspend(Object $result) {
Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
// 檢查是否有異常
ResultKt.throwOnFailure($result);
// 修改 label 為 1
this.label = 1;
// 調用 helloWrold 方法,注意這里把自己當參數傳遞了過去
Object helloWorld = this.mainActivity.helloWorld(this);
if (helloWorld != coroutine_suspended) {
// 如果返回值不等于 coroutine_suspended 表示已經得到正確的返回結果,我們的例子不會執行這里
$result = helloWorld;
break;
} else {
// 表示協程進入 suspend 狀態,等待下次調用 resumeWith() 方法繼續執行
return coroutine_suspended;
}
case 1:
// 第二次執行 resuemWith() 方法,表示已經獲取到 helloWorld() 中的返回值,檢查返回中是否有異常。
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
// 執行打印最后的結果
System.out.println($result);
return Unit.INSTANCE;
}
我們來分析一下上面的代碼:
- 記錄
Continuation
的執行位置的對象是label
,默認label
的值為 0。 -
label
為 0 的邏輯:
- 判斷
result
中是否有異常 - 修改
label
為 1,表示下次resumeWith()
方法執行時,就是case 1
那部分的邏輯。 - 調用
MainActivity#helloWorld()
方法,注意這里將當前的Continuation
對象傳遞給了helloWorld()
方法。 - 這里會判斷
helloWorld()
方法的返回值,如果返回值不為COROUTINE_SUSPEND
,就表示已經拿到返回值,不需要進入suspend
狀態,然后進入label
為 1 的邏輯(我們的代碼不會返回COROUTINE_SUSPEND
);反之就表示沒有獲取到返回值,需要掛起,直接返回COROUTINE_SUSPEND
,等待下次執行resumeWith()
方法恢復,那時也就表示已經獲取到helloWorld()
方法的返回值。
-
label
為 1 的邏輯:
- 判斷
result
中是否有異常。 - 執行打印
result
的結果。 - 這里返回了一個
Unit
而不是COROUTINE_SUSPEND
,就表示當前方法執行完畢了,我們前面提到LaunchContinuation
的父級Continuation
是StandalongCoroutine
,在講BaseContinuationImpl#resumeWith()
方法時講過,當前Continuation
執行完畢后就會執行它的父級Continuation
,也就是后續會執行StandalongCoroutine#resumeWith()
方法,也就是通知StandalongCoroutine
,協程已經執行完畢了。
helloWorld()
的執行流程
在 LaunchContinuation
的執行過程中會調用 MainActivity#helloWorld()
方法,我們再來看看 MainActivity#helloWorld()
方法是如何處理的:
public final java.lang.Object helloWorld(kotlin.coroutines.Continuation<? super java.lang.String> continuation) {
HelloWorldContinuation helloWorldContinuation = null;
if (!(continuation instanceof HelloWorldContinuation)) {
// 構建一個 HelloWorldContinuation 實例,注意這里將 Launch Continuation 作為 HelloWorldContinuation 的父級 Continuation
helloWorldContinuation = new HelloWorldContinuation(this, continuation);
} else {
helloWorldContinuation = (HelloWorldContinuation) continuation;
}
Object suspend = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (helloWorldContinuation.label) {
case 0: {
// 檢查 result 是否有異常
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
// 構建 StringBuilder 對象
StringBuilder stringBuilder = new StringBuilder();
// 將 MainActivity 實例賦值給 HelloWorldContinuation#param1
helloWorldContinuation.param1 = this;
// 將 StringBuilder 實例賦值給 HelloWorldContinuation#param2
helloWorldContinuation.param2 = stringBuilder;
// 修改 label 為 1
helloWorldContinuation.label = 1;
// 執行 hello() 方法
Object result = hello(helloWorldContinuation);
// 返回值為 COROUTINE_SUSPEND 表示當前協程變為 suspend 狀態
if (result == suspend) {
return suspend;
} else {
// 我們的代碼中不會有這種情況
}
}
case 1: {
// 檢查 result 是否有異常
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
MainActivity mainActivity = (MainActivity) helloWorldContinuation.param1;
// 獲取參數 StringBuilder
StringBuilder stringBuilder = (StringBuilder) helloWorldContinuation.param2;
// 獲取上次的 hello() 方法的返回結果
String lastResult = (String) helloWorldContinuation.result;
// 將 hello() 方法的返回結果添加到 StringBuilder 中
stringBuilder.append(lastResult);
// 將 StringBuilder 賦值給 HelloWorldContinuation#param1
helloWorldContinuation.param1 = stringBuilder;
helloWorldContinuation.param2 = null;
// 修改 label 為 2
helloWorldContinuation.label = 2;
// 執行 world() 方法。
Object result = world(helloWorldContinuation);
// 返回值為 COROUTINE_SUSPEND 表示當前協程變為 suspend 狀態
if (result == suspend) {
return suspend;
} else {
// 我們的代碼中不會有這種情況
}
}
case 2: {
// // 檢查 result 是否有異常
kotlin.ResultKt.throwOnFailure(helloWorldContinuation.result);
// 獲取 StringBuilder
StringBuilder stringBuilder = (StringBuilder) helloWorldContinuation.param1;
// 獲取 world() 方法的返回值
String lastResult = (String) helloWorldContinuation.result;
// 將 world() 方法的返回值,添加到 StringBuilder 中
stringBuilder.append(lastResult);
// 將最終結果返回,也就表示當前 Continuation 執行完畢
return stringBuilder.toString();
}
default: {
}
}
throw new UnsupportedOperationException("Method not decompiled: com.tans.coroutine_test.MainActivity.helloWorld(kotlin.coroutines.Continuation):java.lang.Object");
}
我再講Retrofit
的源碼的文章中也講過 Kotlin
的 suspend
函數在編譯處理后,會添加一個 Continuation
對象參數,然后返回值變成 Object
,在 helloWorld()
方法中也得到了印證。
這個邏輯比 LaunchContinuation
中的狀態稍微多了一些,然后各種參數也復雜一點,我們來分析一下:
- 如果
continuation
參數不是HelloWorldContinuation
(這種情況就是LaunchContinuation
),構建一個HelloWorldContinuation
實例,它的父Continuation
是LaunchContinuation
。 -
label
為 0 的邏輯:
- 檢查
result
中是否有異常。 - 構建一個新的
StringBuilder
對象賦值給HelloWorldContinuation#param2
。 - 修改
label
為 1。 - 執行
hello()
方法,如果返回值是COROUTINE_SUSPEND
表示協程進入suspend
狀態,我們的代碼一定會到這段邏輯;反之就直接執行label
為 1 的邏輯。
-
label
為 1 的邏輯:
- 檢查
result
中是否有異常。 - 從
HelloWorldContinaution#param2
中獲取StringBuilder
實例。 - 從
HelloWorldContinuation#result
中獲取hello()
方法的返回值。 - 將
hello()
方法的返回結果寫入到StringBuilder
中。 - 構建一個新的
StringBuilder
對象賦值給HelloWorldContinuation#param1
。 - 修改
label
為 2,執行world()
方法,和執行hello()
方法一樣,在我們的代碼中一定會進入suspend
狀態。
-
label
為 2 的邏輯:
- 檢查
result
中是否有異常。 - 從
HelloWorldContinaution#param1
中獲取StringBuilder
實例。 - 從
HelloWorldContinuation#result
中獲取world()
方法的返回值。 - 將
world()
方法的返回結果寫入到StringBuilder
中。 - 將最終結果返回,也就標志
HelloWorldContinuation
執行完成了。
我們再簡單看看 HelloWorldContinuation#invokeSuspend()
方法的實現:
@Override
public final Object invokeSuspend(Object obj) {
this.result = obj;
this.label |= Integer.MIN_VALUE;
return this.mainActivity.helloWorld(this);
}
代碼非常簡單,將上次的執行的結果寫入到 result
中,然后調用 helloWorld()
方法。
hello()
和 world()
的執行流程
public final Object hello(Continuation<? super String> continuation) {
return delaSuspend(500L, "Hello, ", continuation);
}
public final Object world(Continuation<? super String> continuation) {
return delaSuspend(500L, "World!!", continuation);
}
public final <T> Object delaSuspend(long time, T t, Continuation<? super T> continuation) {
CancellableContinuationImpl cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(continuation), 1);
cancellable$iv.initCancellability();
DelayKt.delay(time, new MainActivity$delaSuspend$2$1(cancellable$iv, t));
Object result = cancellable$iv.getResult();
if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended(continuation);
}
return result;
}
hello()
和 world()
方法都調用了 delaSuspend()
方法用來講 delay()
方法的異步 callback
調用轉換成一個協程 suspend
方法。源碼是這樣的:
suspend fun <T> delaSuspend(time: Long, data: T): T {
return suspendCancellableCoroutine { cont ->
delay(time) {
cont.resumeWith(Result.success(data))
}
}
}
在 delaSuspend()
的處理中會創建一個 CancellableContinuationImpl
對象,它的父級 Continuation
是 HelloWorldContinuation
,這里注意看調用了 IntrinsicsKt.intercepted()
方法來代理 HelloWorldContinuation
,前面我有講過這個方法,它會讓后續的 resumeWith()
方法在 Dispacher
中對應的線程中執行。調用了 DelayKt.delay()
方法然后傳入了一個 Lambda
對象,我們看看 Lambda
對象中的方法執行:
public final void invoke2() {
Continuation continuation = this.$cont;
Result.Companion companion = Result.Companion;
continuation.resumeWith(Result.m122constructorimpl(this.$data));
}
簡單且高效,在回調成功時,直接調用 CancellableContinuationImpl#resumeWith()
方法使協程進入 resume
狀態,后續的邏輯的執行的線程由 Dispatcher
決定。
最后
看到這里你可能還是有點懵的狀態,這是非常正常的,我再來完整的理一下整個流程:
CoroutineScope#launch()
方法中會創建一個 StandaloneCoroutine
對象,然后通過 launch()
方法傳過來的 Lambda
對象構建一個 LaunchContinuation
對象它的父級 Continuation
是 StandaloneCoroutine
,然后調用 LaunchContinuation#resumeWith()
方法標志協程開始。
LaunchContinuation
第一次執行 resumeWith()
時,會調用 helloWorld()
方法,這里會 HelloWorldContinuation
對象,它的父級 Continuation
是 LaunchContinuation
對象。第一次執行 helloWorld()
方法時會調用 hello()
方法,在 hello()
方法中會構建一個 CancellableContinuationImpl
對象,它的父級 Continuation
是 HelloWorldContinuation
,當 hello()
方法的 callback
異步調用成功后會調用 CancellableContinuationImpl#resumeWith()
方法 resume
協程,最終會調用到 HelloWorldContinuation#resumeWith()
方法中去,這里也會觸發 helloWorld()
方法的第二次執行,在第二次執行的過程中會調用 world()
方法,world()
方法的處理邏輯和 hello()
方法一模一樣,回調完成后就會觸發第三次調用 helloWorld()
方法,第三次調用的時候會組合 hello()
和 world()
兩次方法的結果得到最終的結果,然后返回,這時 HelloWorldContinuation
就會調用它的父級的 Continuation
中的 resumeWith()
方法,也就是 LaunchContinuation#resumeWith()
方法,用來通知 LaunchContinuation
表示 helloWorld()
方法已經執行完畢,這個時候是第二次執行 LaunchContinuation#resumeWith()
,這時他也不用再進入 suspend
狀態,又會繼續調用它的父級 Continuation
的 resumeWith()
方法,也就是 StandaloneConroutine#resumeWith()
的方法,它的這個方法調用后也就標志這個協程執行完畢了。
如果到這里還是沒有理解這個過程,推薦你再多看幾遍,一定能夠看懂的,其實就是通過 Continuation
來處理 callback
的套娃操作,當理解了這個過程后,協程的很多地方的源碼你就能夠看得懂了。