Kotlin協程launch原理詳解

正文

launch我們經常用,今天來看看它是什麼原理。

建議: 食用本篇文章之前記得先食用Kotlin協程之createCoroutine和startCoroutine

launch使用

launch我們應該很熟悉瞭,隨便舉個例子:

fun main() {
    val coroutineScope = CoroutineScope(Job())
    coroutineScope.launch {
        println("1969年 葉文潔進入紅岸基地")
        println("1971年 紅岸基地,葉文潔第一次向太陽發送信號,但未發現回波")
        delay(4000L)
        println("1975年 半人馬座三星,三體世界得知地球存在")
    }
    Thread.sleep(5000L)
}

sleep(5000L)和launch內部是在2個線程中,互不幹涉

簡單地使用launch配合delay輸出瞭幾條語句。為瞭瞭解它底層的實現原理,還是老規矩,先反編譯一下。

public final class LaunchTestKt {
    public static final void main() {
        Job unused = BuildersKt__Builders_commonKt.launch$default(CoroutineScopeKt.CoroutineScope(JobKt.Job$default((Job) null, 1, (Object) null)), (CoroutineContext) null, (CoroutineStart) null, new LaunchTestKt$main$1((Continuation<? super LaunchTestKt$main$1>) null), 3, (Object) null);
        Thread.sleep(5000);
    }
}
final class LaunchTestKt$main$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
    int label;
    LaunchTestKt$main$1(Continuation<? super LaunchTestKt$main$1> continuation) {
        super(2, continuation);
    }
    public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
        return new LaunchTestKt$main$1(continuation);
    }
    public final Object invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) {
        return ((LaunchTestKt$main$1) create(coroutineScope, continuation)).invokeSuspend(Unit.INSTANCE);
    }
    public final Object invokeSuspend(Object $result) {
        Object coroutine_suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        switch (this.label) {
            case 0:
                ResultKt.throwOnFailure($result);
                System.out.println("1969年 葉文潔進入紅岸基地");
                System.out.println("1971年 紅岸基地,葉文潔第一次向太陽發送信號,但未發現回波");
                this.label = 1;
                if (DelayKt.delay(4000, this) != coroutine_suspended) {
                    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("1975年 半人馬座三星,三體世界得知地球存在");
        return Unit.INSTANCE;
    }
}

ps:上面這段代碼是通過jadx反編譯apk的方式拿到的源碼,看起來更加人性化。具體的流程是我們用Android Studio寫個掛起函數的demo,然後編譯成apk,然後將apk用jadx反編譯一下,拿到對應class的反編譯Java源碼,這樣弄出來的源碼我感覺比直接通過Android Studio的Tools->Kotlin->Show Kotlin拿到的源碼稍微好看懂一些。

咦,LaunchTestKt$main$1有沒有很眼熟?這不就是前面我們分析startCoroutine原理時得到的匿名內部類麼,簡直一模一樣。這個LaunchTestKt$main$1類對應的是launch的Lambda塊,它本質上是一個Continuation。

startCoroutine原理

LaunchTestKt$main$1相關的原理,在前面已經分析過瞭,這裡不再贅述。這裡主要看一下launch是如何與這個LaunchTestKt$main$1進行關聯的。

launch原理

launch函數如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    //代碼1
    val newContext = newCoroutineContext(context)
    //代碼2
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    //代碼3
    coroutine.start(start, coroutine, block)
    return coroutine
}
  • 將傳入的CoroutineContext構造出新的context
  • 啟動模式,判斷是否為懶加載,如果是懶加載則構建懶加載協程對象,否則就是標準的
  • 啟動協程

context相關的先不看,因為我們demo這裡不是懶加載的所以創建出來的是StandaloneCoroutine,直接看一下start是怎麼啟動協程的。

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
    }
}
public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        start(block, receiver, this)
    }
}

start函數是在父類AbstractCoroutine中實現的,這個start函數裡面又調用瞭一個新的start函數,當我們點擊這個裡面的start函數想進去看源碼時發現,點不過去,點瞭之後還是在當前位置….. ???啥情況

CoroutineStart中找invoke方法

仔細觀察發現,start是一個CoroutineStart對象,直接使用CoroutineStart對象然後後面就接括號瞭,這是類裡面有定義operator invoke方法,然後Kotlin可以通過這種方式來簡化調用。我們直接去CoroutineStart中找invoke方法:

public enum class CoroutineStart {
    DEFAULT,
    LAZY,
    ATOMIC,
    UNDISPATCHED;
    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
        }
    public val isLazy: Boolean get() = this === LAZY
}

CoroutineStart是個枚舉類,定義瞭協程的幾種啟動方式:DEFAULT、LAZY、ATOMIC、UNDISPATCHED。在invoke函數中,根據當前是哪種啟動方式進行開啟協程。

當如果使用ATOMIC的方式,也就是不可取消的協程,就觸發瞭block.startCoroutine(receiver, completion)。有沒有覺得很眼熟,它其實就是我們上節課中分析的啟動協程的關鍵:startCoroutine。

demo中使用的是默認的方式,也就是DEFAULT,它隻不過是在ATOMIC的基礎上,對startCoroutine包裝瞭一下,使其成為可響應取消的協程。而UNDISPATCHED的方式,也就是不分發到其他線程去執行,而是直接在當前線程中進行執行。

startCoroutineCancellable邏輯

來看下DEFAULT之後走的startCoroutineCancellable邏輯:

public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) {
    createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
    completion: Continuation<T>
): Continuation<Unit> {
    val probeCompletion = probeCoroutineCreated(completion)
    return if (this is BaseContinuationImpl)
        //走這裡
        create(probeCompletion)
    else
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function1<Continuation<T>, Any?>).invoke(it)
        }
}

這塊就是前面文章中分析的代碼瞭,launch就算是走完瞭。其本質上是對startCoroutine()這個基礎API進行瞭一些封裝,讓開發者更方便使用。

小結

launch、async之類的是Kotlin協程框架中的中間層,它們是協程構建器。而在協程構建器的內部,實際上是對協程基礎API: createCoroutine{}startCoroutine{}的封裝。它們除瞭擁有啟動協程的基礎能力,還支持傳入CoroutineContext(結構化並發)、CoroutineStart(啟動模式) 等參數,方便開發者使用

以上就是Kotlin協程launch原理詳解的詳細內容,更多關於Kotlin協程launch的資料請關註WalkonNet其它相關文章!

推薦閱讀: