Kotlin协程之旅:探索异步编程的魅力——原理篇

468 阅读17分钟

一、概述

上篇文章我们介绍了 kotlin 协程的具体使用方法,并且理解了什么是协程的创建挂起。这篇文章我们将从源码探索一下协程创建和挂起的底层逻辑

学习记录型的博客,有任何错误请多多包涵,欢迎各位大佬在评论区发表自己的理解和建议。

二、寻找创建函数

众所周知,协程启动最常见的函数就是launch接下来我们就从 launch开始说起。先看一下launch的源码。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, // 协程的上下文,默认是空上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,   // 协程启动方式,默认是默认方式
    block: suspend CoroutineScope.() -> Unit         // 协程执行的挂起函数体
): Job {
    // 创建一个新的协程上下文,基于传入的 context
    val newContext = newCoroutineContext(context)  
    // 如果启动方式是惰性启动(Lazy),创建一个 LazyStandaloneCoroutine,否则创建一个 StandaloneCoroutine
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block)   // 惰性协程:只有被启动时才开始执行
    else
        StandaloneCoroutine(newContext, active = true) // 普通协程:立即启动并处于活动状态
    
    // 启动协程,传入启动方式、协程对象以及执行的 block 函数
    coroutine.start(start, coroutine, block)
    // 返回创建的协程对象也就是 Job,用于表示协程的生命周期
    return coroutine
}

继续进入

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

进入到这里,可能就会卡住了。我们仔细观察肯定会发现,start不是一个变量名吗?为什么能在后面加括号?

这是因为 start 的调用涉及到运算符重载,实际上会调用 CoroutineStart.invoke() 方法。

举个例子大家就明白了:

class Example {
    operator fun invoke() {
        println("Called Example object as a function")
    }
}

fun main() {
    val example = Example()
    example()  // 这实际上是调用 example.invoke()
}

在 Kotlin 中,operator 关键字用于标记特定的函数。运算符重载允许开发者为内置运算符(例如 +, -, *, [], =, 等)定义自定义的行为。这让你可以像使用内置类型一样使用自定义类,并且实现直观、简洁的运算符操作。

既然找到了具体函数的位置,那我们就继续。

@InternalCoroutinesApi
public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>): Unit {
    when (this) {
        DEFAULT -> block.startCoroutineCancellable(completion) // 默认启动方式,支持取消
        
        ATOMIC -> block.startCoroutine(completion) // 原子启动方式,无法被取消

        UNDISPATCHED -> block.startCoroutineUndispatched(completion) // 不使用调度器,直接执行

        LAZY -> Unit // 懒启动,不做任何操作,直到显式启动
    }
}
@InternalCoroutinesApi
public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) {
    createCoroutineUnintercepted(completion)
        .intercepted()
        .resumeCancellableWith(Result.success(Unit))
}

到这里出现了一连串的三个方法调用,我们遵循看函数先看名字的原则,简单翻译一下:create创建Unintercepted未截获;intercepted截获;resumeCancellable恢复可取消的。我们一个一个来看。

1.createCoroutineUnintercepted

ctrl + 左键 进入,人傻了这是哪门子的函数啊。

@kotlin.SinceKotlin public fun <T> (suspend () -> T).createCoroutineUnintercepted(completion: kotlin.coroutines.Continuation<T>): kotlin.coroutines.Continuation<kotlin.Unit> {
    /* compiled code */ 
}

compiled code:编译代码。完蛋,怎么代码还有编译的时候自己生成的?!

这一行注释表示函数的实现是由编译器自动生成的代码,通常在源码中看不到具体的实现。

经过我一番寻找,查资料结果发现了:上面这段代码所在文件的结尾是 .class 。真正的代码存放在末尾带有 Jvm 的文件中。

IntrinsicsJvm.kt下文件 BaseContinuationImpl 抽象类中。

@SinceKotlin("1.3")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
    receiver: R,  // 协程的接收者对象,传递给挂起函数
    completion: Continuation<T>  // 协程完成后的回调接口,处理协程结果或异常
        ): Continuation<Unit> {  // 返回一个 Continuation 对象,表示协程的控制
    // 使用 probeCoroutineCreated 函数处理传入的 completion,通常用于监控协程创建过程
    val probeCompletion = probeCoroutineCreated(completion)

    // 判断当前的挂起函数是否是 BaseContinuationImpl 类型
    // BaseContinuationImpl 是协程实现类,表示协程的基本实现
    return if (this is BaseContinuationImpl)
    // 如果是 BaseContinuationImpl,调用 create 方法创建协程
    // create 方法会使用 receiver 和 probeCompletion 来创建一个新的协程
    create(receiver, probeCompletion)
    else {
        // 如果不是 BaseContinuationImpl,则使用 createCoroutineFromSuspendFunction 来创建协程
        // 这个方法将挂起函数转化为协程,并返回一个新的 Continuation 对象
        createCoroutineFromSuspendFunction(probeCompletion) {
            // 将当前的挂起函数(this)转化为一个 Function2 类型,并调用其 invoke 方法
            // Function2 接口的签名是 (receiver: R, continuation: Continuation<T>) -> Any?
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}

乍一看,有个不认识的actual关键字:

在 Kotlin 中,actual 关键字用于与 expect 配合使用,表示对特定平台的实现。这是 Kotlin 多平台(Kotlin Multiplatform)特性的核心概念之一。

expectactual 的配合使用:

Kotlin 多平台开发允许你为不同的平台(如 Android、iOS、JVM、JavaScript 等)编写共享代码,并为每个平台提供平台特定的实现。expectactual 是实现这一目标的关键机制。

  • expect:在共享代码中声明一个接口或属性,表示该接口或属性的声明需要在不同平台上有不同的实现。
  • actual:在平台特定的代码中提供 expect 声明的实际实现

要在一段代码中迅速抓住关键:create(receiver, probeCompletion),创建一个新的协程。所以,从这里开始,正式开始创建协程了。

但是......

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

has not been overridden,也就是说,我们看到的是没有重写的代码,那重写了的代码到底在哪里呢?

最有可能的地方就是在创建的时候,我们随便写一个用 launch 启动的协程。

fun createCoroutine() {
    val scope = CoroutineScope(Dispatchers.Main)
    scope.launch {
        delay(1000)
    }
}

运用 AS 自带的反编译工具,编译一下

public final class LaunchTestKt {
   public static final void createCoroutine() {
      // 创建一个 CoroutineScope,指定协程执行的上下文是主线程(Dispatchers.getMain())
      CoroutineScope scope = CoroutineScopeKt.CoroutineScope((CoroutineContext)Dispatchers.getMain());

      // 调用 BuildersKt.launch$default 创建协程并启动,传入的匿名函数表示协程的行为
      BuildersKt.launch$default(scope, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
         int label;  // 协程的状态机标签,用来标识协程执行的步骤

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            // 获取挂起的标志符,表示协程是否挂起
            Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

            // 根据协程的 label,判断协程当前执行到了哪个阶段
            switch (this.label) {
               case 0:  // 第一步:协程第一次调用
                  ResultKt.throwOnFailure($result);  // 如果上一步失败,抛出异常
                  this.label = 1;  // 设置为下一阶段(label = 1)

                  // 调用 delay(1000L),并检查是否挂起,如果挂起则返回
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;  // 协程挂起,等待 1 秒后恢复
                  }
                  break;
               case 1:  // 第二步:协程恢复后继续执行
                  ResultKt.throwOnFailure($result);  // 检查是否恢复失败
                  break;
               case 2:  // 第三步:协程最终结束
                  ResultKt.throwOnFailure($result);  // 检查是否恢复失败
                  return Unit.INSTANCE;  // 协程完成
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");  // 如果出现未定义的状态,则抛出异常
            }

            // 第三步,协程设置为再次延迟 2 秒后恢复
            this.label = 2;
            if (DelayKt.delay(2000L, this) == var2) {
               return var2;  // 再次挂起,等待 2 秒后恢复
            } else {
               return Unit.INSTANCE;  // 如果没有挂起,直接返回,协程完成
            }
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            // 创建协程的初始化对象
            Intrinsics.checkNotNullParameter(completion, "completion");  // 校验 completion 不为空
            Function2 var3 = new <anonymous constructor>(completion);  // 创建新的 Function2 对象
            return var3;  // 返回新的 Continuation 实例
         }

         // invoke 方法,传入两个参数用于启动协程
         public final Object invoke(Object var1, Object var2) {
            // 创建协程并调用 invokeSuspend 开始执行协程
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), 3, (Object)null);  // 启动协程,传入参数 3 表示第 3 个协程任务
   }
}

很容易就发现了create 函数,原来是在这里自动创建的!

反编译的 java 代码都看了,那不妨再看看字节码。我们只看能看懂的部分,比如下面的 new 了一个实例。

这个实例在当前字节码中还能找到

提取一下有用的信息:在 launch lambda 表达式中输入的内容会自动生成一个叫LaunchTestKt$createCoroutine$1的类,这个类继承了SuspendLambda实现了Function2 接口。

final class com/jxdx/corecodelibrary/LaunchTestKt$createCoroutine$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2 {
  // access flags 0x11
  public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
  
  public final create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
  // access flags 0x11
  public final invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    
  final static INNERCLASS com/jxdx/corecodelibrary/LaunchTestKt$createCoroutine$1 null null
  // compiled from: launchTest.kt
}

那就有个疑问了 lambda 表达式中的代码都被放到哪个方法中了?左边选中,右边 AS 自动选中,原来里面的代码都被放到了invokeSuspend这个函数的里面去了,所以我们也就得出个结论:invokeSuspend中存放的是协程的具体实现逻辑

(理一下思路)这里思路回到最先研究的问题,很容易就明白了,createCoroutineUnintercepted返回的是一个 SuspendLambda 实例。找到SuspendLambda 类看一下。

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
}

一步步进入,我们能发现其继承关系为: SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation

由以上的继承关系我们可以确认一点,协程体本质上就是一个继体。

2.Continuation继体

Continuation 是 Kotlin 协程中的一个核心接口,负责协程的状态管理,特别是挂起(suspend)和恢复(resume)协程的操作。它充当了协程的控制器,维护协程执行的状态,并处理协程的恢复逻辑。

先大致了解一下Continuation 在协程中的工作流程(要结合前面的代码分析思考):

  1. 挂起点的创建:当协程遇到一个挂起点(例如 delay() 或者其他挂起函数)时,Kotlin 会创建一个 Continuation 对象。这个对象保存了当前协程的状态,并将控制权返回给调用者(通常是调度器)。
  2. 恢复协程:当挂起操作完成,协程需要恢复执行时,调用 resumeWith 方法。resumeWith恢复协程的执行,继续执行挂起点之后的代码。
  3. Continuation suspend 函数:在协程中的 suspend 函数内部,会自动创建和使用 Continuation 对象来管理协程的挂起和恢复。在实际的 suspend 函数中,协程体通常会通过 Continuation 来挂起自己,并等待某个条件(如网络请求完成、定时器触发等)来恢复执行。
interface Continuation<in T> {
    val context: CoroutineContext  // 协程的上下文
    fun resumeWith(result: Result<T>)  // 恢复协程执行
}

总结一下上面所说的:调用createCoroutineUnintercepted()创建了SuspendLambda实例,也就是Continuation 继体。

3.intercepted

拦截,从上面三个一连串的调用可以看出,每个协程SuspendLambda都会被拦截,也就是每创建一次,就拦截一次。

@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this
public fun intercepted(): Continuation<Any?> =
    intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
    .also { intercepted = it }

MainCoroutineDispatcher 类中

public abstract class CoroutineDispatcher :  
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor 

实现了 CoroutineDispatcher 方法:

public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
    DispatchedContinuation(this, continuation)

interceptContinuation 这个方法的目的是将一个原始的 Continuation 对象通过某种方式拦截,并且将其转化为一个新的 Continuation 对象。具体来说,它将原始的 Continuation 封装在一个 DispatchedContinuation 对象中,并返回这个新的对象。

拦截到的是一个继体,同样的,返回的居然也是一个 Continuation 继体 注意,这里主体变了(重新包装了一遍),变成了拦截之后的Continuation 继体,也就是DispatchedContinuation

很自然的就会想到一个问题,为什么要这样子对Continuation进行包装,创建DispatchedContinuation的意义到底是什么?

当协程恢复时,DispatchedContinuation 的 resumeWith() 方法会被调用:

class DispatchedContinuation(
    private val dispatcher: CoroutineDispatcher,
    private val continuation: Continuation<T>
) : Continuation<T> {
    override fun resumeWith(result: Result<T>) {
        dispatcher.dispatch(context) {  // 切换到目标线程
            continuation.resumeWith(result)  // 执行原始 Continuation
        }
    }
}

通过拦截 Continuation,协程可以在挂起后恢复时切换到指定线程(如主线程、IO 线程)。

4.resumeCancellableWith

从名字看就知道,这部分是关于恢复(resume)的。

@InternalCoroutinesApi
public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,        // 协程执行的结果,可以是成功的结果或失败的异常
    onCancellation: ((cause: Throwable) -> Unit)? = null  // 可选的取消回调,处理协程取消时的清理工作
): Unit = when (this) {  // 检查当前的 Continuation 类型
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation) // 如果是 DispatchedContinuation 类型,调用其自定义的 resumeCancellableWith 方法
    else -> resumeWith(result) // 否则调用 resumeWith 方法直接恢复协程
}

先看一下resumeCancellableWith()

// We inline it to save an entry on the stack in cases where it shows (unconfined dispatcher)
// It is used only in Continuation<T>.resumeCancellableWith
@Suppress("NOTHING_TO_INLINE")
inline fun resumeCancellableWith(
    result: Result<T>,  // 协程的执行结果
    noinline onCancellation: ((cause: Throwable) -> Unit)?  // 可选的取消回调函数
) {
    // 将 Result 转换为协程状态,同时传入取消回调函数
    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)  // 恢复协程并传递执行结果
            }
        }
    }
}

同样的抓住重点部分,分为两种情况:

  1. 需要调度(dispatcher.isDispatchNeeded(context)true)则通过调度器派发协程,dispatcher 是协程的调度器(如 Dispatchers.IO、Dispatchers.Main)。dispatch() 方法将协程的执行逻辑(this)提交到目标线程的队列中。
  2. 不需要调度,直接在当前线程执行。

看到 dispatcher 立马反应过来,这里开始分发了。

public open fun dispatch(context: kotlin.coroutines.CoroutineContext, block: kotlinx.coroutines.Runnable /* = java.lang.Runnable */): kotlin.Unit { /* compiled code */ }

这里注意到个关键点 blockRunnable 类型的。

dispatch 有什么用?这里举个例子:

Dispatchers.Main 的 dispatch 方法通过 Handler 将任务派发到主线程:

override fun dispatch(context: CoroutineContext, block: Runnable) {
    // 获取主线程的 Handler
    val handler = Handler(Looper.getMainLooper())
    // 将任务提交到主线程的消息队列
    handler.post(block)
}

所以dispatch 就是用来调度任务的。

这里还出现了resumeWith()我们看一下,这个函数究竟是怎么恢复协程的。resumeWith()很熟悉啊,就是Continuation接口里面的一个方法,最终找到是在BaseContinuationImpl 类中实现的。

public final override fun resumeWith(result: Result<Any?>) {
    // 该循环展开递归,通过非递归方式恢复协程的执行,
    // 使得恢复过程的堆栈跟踪更加清晰简洁
    var current = this  // 当前的 continuation 对象,表示当前正在恢复的协程
    var param = result   // 传入的结果,通常是协程的结果或异常

    while (true) {  
        // 通过循环展开,避免递归带来的栈深度问题
        // 在每次恢复协程时,触发调试探针,用于追踪哪些协程已经被恢复,
        // 这样调试工具可以更精确地跟踪协程调用栈的恢复进度
        probeCoroutineResumed(current)

        with(current) {
            // 获取该 continuation 的 completion(即协程的后续执行)
            val completion = completion!! // 快速失败:如果 completion 为 null,则抛出异常

            // 定义协程恢复后的结果 outcome
            val outcome: Result<Any?> =
            try {
                // 执行协程体中的挂起函数,传入参数 `param`。 
                val outcome = invokeSuspend(param)
                if (outcome === COROUTINE_SUSPENDED) return // 如果协程没有完成,挂起当前协程
                Result.success(outcome) // 如果协程执行完毕,返回成功结果
            } catch (exception: Throwable) {
                Result.failure(exception) // 如果协程执行过程中抛出异常,捕获并返回失败结果
            }

            releaseIntercepted()  // 释放拦截器,表示当前状态机实例终结

            // 如果 completion 还是一个 BaseContinuationImpl(递归展开的基础协程体),
            // 继续展开栈,并将 outcome 作为参数传递到下一个 continuation
            if (completion is BaseContinuationImpl) {
                current = completion  // 将当前 continuation 设为 completion,继续展开栈
                param = outcome        // 传递当前的 outcome 作为下一个 continuation 的参数
            } else {
                // 到达协程的最顶层(没有更多的 continuation),
                // 调用 completion 的 resumeWith 方法恢复最终的协程结果
                completion.resumeWith(outcome)
                return // 完成协程恢复,退出方法
            }
        }
    }
}

这段代码通过 循环展开递归 来实现协程的恢复,避免了递归调用导致的栈溢出问题。它同时还包括了 异常处理调试探针拦截器释放 等机制。通过这种方式,协程可以被平滑地恢复,并且能够在协程被恢复时,准确地处理协程链条中的每一个 Continuation 对象,最终恢复协程的执行并传递结果。

注意,这里又重新调用了 val outcome = invokeSuspend(param) if (outcome === COROUTINE_SUSPENDED) return,这个函数很熟悉的,我们在反编译过来的 java 代码中看到过(这里的逻辑想仔细思考一下就能理通了)。接下来介绍一下协程的状态机。

5.状态机

协程本质上是通过一个状态机来执行的。每次挂起操作都会将协程的执行状态(包括当前执行到的位置)保存起来。当协程恢复时,它会从保存的状态继续执行。通常使用 label(标签)来表示协程的执行状态。

之前的代码如下:

public final class LaunchTestKt {
   public static final void createCoroutine() {
      BuildersKt.launch$default(scope, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
         int label;  // 协程的状态机标签,用来标识协程执行的步骤

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            // 获取挂起的标志符,表示协程是否挂起
            Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

            // 根据协程的 label,判断协程当前执行到了哪个阶段
            switch (this.label) {
               case 0:  // 第一步:协程第一次调用
                  break;
                
               case 1:  // 第二步:协程恢复后继续执行
                  break;
                
               case 2:  // 第三步:协程最终结束
                  return Unit.INSTANCE;  // 协程完成
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");  // 如果出现未定义的状态,则抛出异常
            }

            // 第三步,协程设置为再次延迟 2 秒后恢复
            this.label = 2;
            if (DelayKt.delay(2000L, this) == var2) {
               return var2;  // 再次挂起,等待 2 秒后恢复
            } else {
               return Unit.INSTANCE;  // 如果没有挂起,直接返回,协程完成
            }
         }
         
      }), 3, (Object)null);  // 启动协程,传入参数 3 表示第 3 个协程任务
   }
}

也就是说他是通过上面讲的 resume 恢复过来,调用的val outcome = invokeSuspend(param) ,这方法这个状态切换代替了传统的回调,使得效率更高。而且这样就避免了传统线程的切换开销,并允许开发者编写异步的代码,像编写同步代码一样直观。

这就是挂起的真正原理。如果前面的outcome返回了COROUTINE_SUSPENDED,就直接 return 掉了。那就意味着方法被暂停了,协程也被暂停了。

6.疑问

看到这里你们绝对和我一样还有一个很大的疑问,异步操作的具体代码跑哪去?为什么没看到?我的并行代码在哪里执行了?

以 delay() 来举例子。

suspend fun delay(timeMillis: Long) {
    suspendCoroutine<Unit> { continuation ->
        // 使用某个调度器(比如 Default 或 Main)安排一个延迟任务
        val dispatcher = Dispatchers.Default

        // 安排一个任务,等待指定的时间(timeMillis)
        dispatcher.scheduleResumeAfterDelay(timeMillis) {
            // 当延迟结束后,恢复协程
            continuation.resume(Unit)
        }
    }
}
// Default 调度器的一个简单实现(比如基于线程池或事件循环)
object Dispatchers {
    val Default: CoroutineDispatcher = object : CoroutineDispatcher {
        override fun scheduleResumeAfterDelay(timeMillis: Long, block: () -> Unit) {
            // 使用某种机制安排任务(比如 Timer、ScheduledExecutorService、事件循环等)
            // 这里以简单的 Timer 为例
            Timer().schedule(object : TimerTask() {
                override fun run() {
                    block()  // 延迟后执行恢复协程的代码
                }
            }, timeMillis)
        }
    }
}

前面提过一个Runnable,转递给 dispatcher 了,也就是说具体怎么执行的是由调度器决定的,这里就不深入讨论了。

三、总结

1. 协程的核心

  • Continuation继体:协程的执行体需要一个 Continuation 来跟踪执行的进度。Continuation 记录了协程当前的状态(如执行进度、局部变量等),它会在协程挂起和恢复时进行切换。
  • invokeSuspend:协程的实际执行体在 invokeSuspend 方法中定义,挂起和恢复的逻辑都在这里。协程开始时调用 invokeSuspend,挂起时返回 COROUTINE_SUSPENDED,恢复时继续执行。

2. 挂起恢复

  • 挂起函数suspend 关键字标记的函数可以挂起协程的执行。挂起函数会返回一个特殊的值(COROUTINE_SUSPENDED),这表示当前协程需要暂停,直到某些条件满足后再恢复执行。
  • 恢复协程:协程恢复时,Continuation.resumeWith 方法会被调用,协程会从上次挂起的位置继续执行。恢复时,invokeSuspend 会被再次调用,继续执行协程的代码。

3. 调度与执行

  • 协程调度器 ( CoroutineDispatcher ) :协程的执行是由调度器管理的,调度器负责确定协程在何时、在哪个线程上执行。

4. 状态机

  • 协程的状态机:每个协程的执行逻辑都可以看作一个状态机。状态机的状态由 label(通常是一个整数)表示,不同的 label 表示协程执行的不同阶段。每次协程挂起时,label 会改变,以便在恢复时继续从正确的地方执行

四、鸣谢

Android 技术指导 :小王学长