深入理解kotlin协程

1,166 阅读3分钟

举个栗子

通过下面的例子我们一起来一步一步来剖析kotlin协程

界面

布局

界面逻辑

```
class WanAndroidActivity:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_wanandroid)

        btnStart.setOnClickListener {
             startLaunch()
        }
    }

    private fun startLaunch() {
        GlobalScope.launch {
            println("启动协程-- Thread name = ${Thread.currentThread().name}")
            val wanAndroidApi = NetContext.get().create(WanAndroidApi::class.java)

            val token = login()
            println("token = $token")

            val users = getUserList(token)
            println("users = $users")

        }
    }

    private suspend fun getUserList(token: String): List<String> {
        delay(2000)
        return listOf("abc","bcd","cde")
    }

    private suspend fun login(): String {
        delay(2000)
        return "token"
    }


}
```

界面的布局和逻辑比较简单

字节码分析

对代码进行反编译,点击AS Tools->kotlin->show kotlin bytecode

在上面的类定义了三个函数分别是 getUserList(token: String)login()startLaunch() ,我们逐个来看:

getUserList(token: String)

image.png

login()

image.png

startLaunch()

@QVY5G)UTF7MLZQ}QS~$G.png

我们把startLaunch() 反编译的代码,其中Function2的实现先抽出来,简化一下代码,如下:

private final void startLaunch() {
    CoroutineScope coroutineScope = CoroutineScope.INSTANCE;
    CoroutineContext context = null;
    CoroutineStart coroutineStart = null;
    Function2 function2 = SuspendLambda(){};
    BuildersKt.launch$default(coroutineScope,context,coroutineStart,function2, null);
}

SuspendLambda怎么来的:

为什么Function2 创建的实例是SuspendLambda,这个我们可以在字节码里可以看出

image.png

在字节码里调用了指令 NEW com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1 来创建对象

image.png

从上面的字节码可以看出com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1对象继承了SuspendLambda并且实现了Function2接口。

BuildersKt.launch$default 方法的实现:

image.png

image.png

直至此我们通过反编译可以看到协程体生成的代码内容是什么,即SuspendLambda

协程的创建、启动、恢复

我们来看CoroutineScope.launch{}的源码,来看一下协程的创建、启动和恢复

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: suspend CoroutineScope.() -> UnitSuspendLambda

//AbstractCoroutine.kt
//receiver:StandaloneCoroutine
//completion:StandaloneCoroutine<Unit>

public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    initParentJob()
    start(block, receiver, this)
}

start(block, receiver, this) 调用 CoroutineStart 中的 invoke 方法


//receiver:StandaloneCoroutine
//completion:StandaloneCoroutine<Unit>

@InternalCoroutinesApi
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
    when (this) {
        CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
        CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
        CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
        CoroutineStart.LAZY -> Unit // will start lazily
    }

会走到CoroutineStart.DEFAULT

//Cacellable.kt
//receiver:StandaloneCoroutine
//completion:StandaloneCoroutine<Unit>

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
    }
    

调用3个方法,createCoroutineUnintercepted()intercepted()resumeCancellableWith()

先看看createCoroutineUnintercepted()


//IntrinsicsJvm.kt

@SinceKotlin("1.3")
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)
    else {
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}

可以看到是调用 BaseContinuationImplcreate 的方法

public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
    throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
}

create方法具体实现是什么,在 com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1 的字节码找到相对应的实现


  // access flags 0x11
  // signature (Ljava/lang/Object;Lkotlin/coroutines/Continuation<*>;)Lkotlin/coroutines/Continuation<Lkotlin/Unit;>;
  // declaration: kotlin.coroutines.Continuation<kotlin.Unit> create(java.lang.Object, kotlin.coroutines.Continuation<?>)
  public final create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)
    @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
   L0
    ALOAD 2
    LDC "completion"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
    NEW com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1
    DUP
    ALOAD 0
    GETFIELD com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1.this$0 : Lcom/maiml/pdfdemo/wanandroid/WanAndroidActivity;
    ALOAD 2
    INVOKESPECIAL com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1.<init> (Lcom/maiml/pdfdemo/wanandroid/WanAndroidActivity;Lkotlin/coroutines/Continuation;)V
    ASTORE 3
    ALOAD 3
    ARETURN
   L1
    LOCALVARIABLE this Lkotlin/coroutines/jvm/internal/BaseContinuationImpl; L0 L1 0
    LOCALVARIABLE value Ljava/lang/Object; L0 L1 1
    LOCALVARIABLE completion Lkotlin/coroutines/Continuation; L0 L1 2
    MAXSTACK = 4
    MAXLOCALS = 4

上面的代码可以看到是创建出了 com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1 并返回

在反编译的Java类也可以看出

image.png

可能有疑问为什么BaseContinuationImplcreate 的方法的实现要在com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1 字节码找,在上面我们分析了com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1SuspendLambda,而SuspendLambda的继承关系是:SuspendLambda->ContinuationImpl->BaseContinuationImpl

intercepted()


//IntrinsicsJvm.kt
//这里的 this 是 `com/maiml/pdfdemo/wanandroid/WanAndroidActivity$startLaunch$1` 实例 - ContinuationImpl的子类


@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this
    

//ContinuationImpl
// context[ContinuationInterceptor]是 CoroutineDispatcher 实例

public fun intercepted(): Continuation<Any?> =
    intercepted
        ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
            .also { intercepted = it }
            
            
 //CoroutineDispatcher
 
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
    DispatchedContinuation(this, continuation)
       
    

创建DispatchedContinuation 用于线程调度

resumeCancellableWith()


// this 是 DispatchedContinuation
public fun <T> Continuation<T>.resumeCancellableWith(result: Result<T>) = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result)
    else -> resumeWith(result)
}



inline fun resumeCancellableWith(result: Result<T>) {
    val state = result.toState()
    //判断是否需要线程调度,我们这里需要
    if (dispatcher.isDispatchNeeded(context)) {
        _state = state
        resumeMode = MODE_CANCELLABLE
        dispatcher.dispatch(context, this)
    } else {
        executeUnconfined(state, MODE_CANCELLABLE) {
            if (!resumeCancelled()) {
                resumeUndispatchedWith(result)
            }
        }
    }
}

dispatcher是:

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

newCoroutineContext创建 coroutine 的时候如果没有指定Dispatchers会 添加默认Dispatchers.Default

@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()



internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"

internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
    when (value) {
        null, "", "on" -> true
        "off" -> false
        else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
    }
}

internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
    if (useCoroutinesScheduler) DefaultScheduler else CommonPool
    

COROUTINES_SCHEDULER_PROPERTY_NAME的值默认是on,即会创建DefaultSchedulerdispatcher.dispatch 是的 DefaultScheduler类的dispatch方法

//Dispatcher.kt

override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
    try {
        coroutineScheduler.dispatch(block)
    } catch (e: RejectedExecutionException) {
        DefaultExecutor.dispatch(context, block)
    }
    
    
//CoroutineScheduler.kt
 
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
    trackTask() // this is needed for virtual time support
    val task = createTask(block, taskContext)
    // try to submit the task to the local queue and act depending on the result
    val notAdded = submitToLocalQueue(task, fair)
    if (notAdded != null) {
        if (!addToGlobalQueue(notAdded)) {
            // Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
            throw RejectedExecutionException("$schedulerName was terminated")
        }
    }
    // Checking 'task' instead of 'notAdded' is completely okay
    if (task.mode == TaskMode.NON_BLOCKING) {
        signalCpuWork()
    } else {
        signalBlockingWork()
    }
}

internal fun createTask(block: Runnable, taskContext: TaskContext): Task {
    val nanoTime = schedulerTimeSource.nanoTime()
    if (block is Task) {
        block.submissionTime = nanoTime
        block.taskContext = taskContext
        return block
    }
    return TaskImpl(block, nanoTime, taskContext)
}

 

image.png

任务调度到会执行TaskIpmlrun方法,然后调用的是block.run(),block是DispatchedContinuation并且继承了DispatchedTask,因此调用到DispatchedTask的run方法

image.png

如果不出现异常会走到 continuation.resume()

// Continuation.kt

public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))
    

Continuation对象是分析的SuspendLambda,它的继承关系是:SuspendLambda->ContinuationImpl->BaseContinuationImpl,最终会调用到BaseContinuationImpl的resumeWith方法

image.png

调用 SuspendLambdainvokeSuspend方法

image.png

看到72-76行,判断var10000是否等于COROUTINE_SUSPENDED,如果等于COROUTINE_SUSPENDED代表没有可用结果,需要挂起等待可用结果返回

看看调用的var13.login()方法会返回什么

只贴关键代码

只贴关键代码

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
}

返回suspendCancellableCoroutine即COROUTINE_SUSPENDED,需要挂起等待结果返回,DelayKt.delay()我们可以类比成Handler.postDelayed,执行完成后会调用会continuation.resume() ->BaseContinuationImpl.resumeWith()->SuspendLambda.invokeSuspend() 进行恢复。

总结

image.png

  • 执行到login()方法时,login()方法会返回COROUTINE_SUSPENDED,在71行把label 设置为1,执行var10000 == var7 然后return。为什么login()方法会返回COROUTINE_SUSPENDED,因为在login()方法调用delay()方法,协程会被挂起,代表当前结果不可用。

  • login()方法执行结束,并有可用结果返回,回调到invokeSuspend方法,此时的label=1,把结果赋值给var10000,然后break执行下面的逻辑。

  • 来到89行,token=var10000,并把label设置为2,执行getUserList()方法,因为getUserList()方法内部也是调用delay()方法,因此协程也会被挂起,返回COROUTINE_SUSPENDED,执行var10000 == var7 然后return,继续等待可用结果返回。

  • getUserList()方法执行结束,并有可用结果返回,回调到invokeSuspend方法,此时的label=2,把结果赋值给var10000,并执行break label17,来到101行执行剩下的逻辑。

#参考

Kotlin协程之深入理解协程工作原理 - 掘金 (juejin.cn)

Kotlin Coroutines(协程) 完全解析(二),深入理解协程的挂起、恢复与调度 - 简书 (jianshu.com)

Kotlin协程挂起(3)_十一月Siy的博客-CSDN博客