一、Kotlin协程系列:协程的创建与启动,挂起和恢复

1,969 阅读16分钟

Kotlin协程系列文章:《一、Kotlin协程系列:协程的创建与启动,挂起和恢复》
Kotlin协程系列文章:《二、Kotlin协程系列:协程的线程调度》

本文是分析Kotlin协程系列文章的第一篇,在本篇中分为2大块分析协程创建和启动流程;挂起和恢复流程。其中在分析协程的创建启动流程中,我们将解答:

  1. kotlin编译器是如何处理suspend Lambda的,或者说处理suspend关键字的?
  2. 传入协程lambda的代码是如何被执行到的?

在分析协程的挂起和恢复流程中,我们将解答:

  1. 协程挂起的本质是什么?又是如何被挂起的?挂起会阻塞执行线程吗?
  2. 协程是如何做到用同步调用的方式执行异步代码?
  3. 协程在挂起后,又是如何恢复执行的?

一、协程的创建与启动

一般来说开启协程有2种方式:

  • CoroutineScope.launch()
  • CoroutineScope.async()

我们以第一种来做为主流程分析,其他方式殊途同归。

1.1 suspend Lambda表达式的编译处理

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //1. 启动一个协程
        GlobalScope.launch {
            Log.d(TAG, "onCreate: print in launch.")
        }
    }
}

当调用GloubalScope.launch()方法开启一个协程时,调用的是GlobalScope的扩展函数,该函数原型如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}

观察函数签名原型可以知道,在启动协程时传入的lambda表达式是一个suspend CoroutineScope.() -> Unit,而Kotlin编译器其实会将传入的协程体进行处理,生成一个继承于SuspendLambda的实体类。这块就必须要反编译才能看到了,详见1.1.1流程。

1.1.1 suspend Lambda的编译产物

//MainActivity.class
public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     Job unused = BuildersKt__Builders_commonKt
        .launch$default(GlobalScope.INSTANCE,
                        null, null, new MainActivity$onCreate$1(null), 3, null);
 }

//MainActivity$onCreate$1.class
final class MainActivity$onCreate$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
    int label;

    public MainActivity$onCreate$1(Continuation<? super MainActivity$onCreate$1> continuation) {
        super(2, continuation);
    }

    //1. 重写了BaseContinuationImpl.create()方法。
    @Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
        // 直接new了一个自身实例出来返回。
        return new MainActivity$onCreate$1(continuation);
    }
    
	...

    //2. 重写了BaseContinuationImpl.invokeSuspend()方法。这里是状态机实现核心方法。
    @Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object obj) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        switch (this.label) {
            case 0:
                ResultKt.throwOnFailure(obj);
                //协程的实际逻辑代码
                Log.d(MainActivity.TAG, LiveLiterals$MainActivityKt.INSTANCE.m4146xf96cab04());
                return Unit.INSTANCE;
            default:
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }
    }
}

这里可以看到在源码中传入的block参数在这里被生成一个MainActivity$onCreate$1类型的实例,该类型实例有如下特点:

  1. 继承于SuspendLambda
  2. 实现了BaseContinuationImpl.create()方法,该方法用于创建自身实例。
  3. 实现了BaseContinuationImpl.invokeSuspend()方法,该方法包含了真正的协程逻辑代码,并且用状态机的方式实现了协程的挂起/恢复逻辑。

为了探究自动生成的实例类型究竟是何物,我们继续沿着它继承的SuspendLambda往上看。

1.1.2 SuspendLambda

@SinceKotlin("1.3")
// Suspension lambdas inherit from this class
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

  1. SuspendLambda其实没有扩展什么实际功能,只扩展了个无关紧要的toString()方法。
  2. 注意这里将conmpletion成员变量继续传给ContinuationImpl父类。

接下来沿着ContinuationImpl一步步往上探究续体。

1.1.3 ContinuationImpl

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    //1.ContinuationImpl的Context使用的是completion?.context
    constructor(completion: Continuation<Any?>?) 
    : this(completion, completion?.context)

    public override val context: CoroutineContext
        get() = _context!!

    @Transient
    private var intercepted: Continuation<Any?>? = null

    //2. 获取拦截器
    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

    protected override fun releaseIntercepted() {
        ...
    }
}

ContinuationImpl继承至BaseContinuationImpl:

  1. 注意这里,ContinuationImpl所使用的协程上下文其实用的是completion.context。而将completion续体继续往父类传递。

  2. ContinuationImpl主要是扩展增加了intercepted(),这对于后续协程的线程调度起到关键作用。

1.1.4 BaseContinuationImpl

internal abstract class BaseContinuationImpl(
    // 这个续体将会赋值为父续体
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    
    // 该方法是启动协程的关键触发方法,此方法也包含了挂起和恢复的主要流程。
    public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
           	...
            with(current) {
                 // fail fast when trying to resume continuation without completion
                val completion = completion!!
                 // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        // 调用协程续体代码。
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

    //此方法在自动生成的SuspendLambda实例时会被实现,该方法用于实现协程的状态机。
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?

    protected open fun releaseIntercepted() {
        // does nothing here, overridden in ContinuationImpl
    }

    //此方法在自动生成的SuspendLambda实例时会被实现,该方法用于创建该实例的。
    public open fun create(completion: Continuation<*>): Continuation<Unit> {
        throw UnsupportedOperationException("create(Continuation) has not been overridden")
    }

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

1.1.5 总结

  • 我们通过反编译搞清楚了suspend CoroutineScope.() -> Unit函数类型其实在编译器的处理过后,会生成一个继承于SuspendLambda类型的类,并且传入CoroutineScope.launch()方法的也是这个实例的类型。

  • 自动生成的实例实现了create()方法和invokeSuspend()方法。

  • SuspendLambda继承于ContinuationImpl类型,实现了intercepted()方法,用于取出当前协程上下文中的调度器。SuspendLambda的协程上下文使用的是completion的上下文completion续体是1.2.1小节中启动时,创建的一个StandaloneCoroutine续体。

  • ContinuationImpl继承于BaseContinuationImpl类型,该类型有关键的resumeWith()方法和completion: Continuation<Any?>?成员变量。

1.2 协程的启动流程

上一小节中,预先分析了suspend lambda在被Kotlion编译器处理后的代码,本小节接着分析协程的启动流程。

1.2.1 CoroutineScope.launch()

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    //1. 复制Context
    val newContext = newCoroutineContext(context)
    //2. 用Context创建一个新的续体,注意这个续体将会被作为结束回调。
    val coroutine = if (start.isLazy)
    LazyStandaloneCoroutine(newContext, block) else
    //一般是进入这个分支。
    StandaloneCoroutine(newContext, active = true)
    //3.启动协程。
    coroutine.start(start, coroutine, block)
    return coroutine
}

一般的,我们使用CoroutineScope.launch()启动一个协程,步进此方法源码有如下几个要点:

  1. 复制Context,并默认使用了Dispatchers.Default
  2. 创建了一个新的协程体StandaloneCoroutine,后问我们在本文中称之为协程体1,注意这个StandaloneCoroutine继承Continuation<T>,所以它其实也是个续体。
  3. 调用StandaloneCoroutine.start()方法,注意这里第二个参数又有把自己传入当作参数。详见1.2.2。

1.2.2 StandaloneCoroutine.start()

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

//CorountineStart.kt
 @InternalCoroutinesApi
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
    when (this) {
        //2. 进入这个调用
        DEFAULT -> block.startCoroutineCancellable(receiver, completion)
        ATOMIC -> block.startCoroutine(receiver, completion)
        UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
        LAZY -> Unit // will start lazily
    }
    
  1. 这里其实是由于Kotlin的语法糖,所以实际调用了CoroutineStart.invoke()方法,由于在上面中,我们用默认的CoroutineStart.DEFAULT。注意这里的receiver等于之前新建的coroutine,而这里的this也是coroutine
  2. 可以看到其实调用的是block的方法。

1.2.3 真正启动协程 startCoroutineCancellable()

//Cancellable.kt
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) =
    runSafely(completion) {
        //1. 创建协程
        createCoroutineUnintercepted(receiver, completion)
            //2. 协程线程调度切换,详细见1.2.5
            .intercepted()
            //3. 出发协程开启工作,详见第1.2.6
            .resumeCancellableWith(Result.success(Unit), onCancellation)
    }

可以看到,startCoroutineCancellable其实是(suspend (R) -> T)类型的一个扩展函数。这里三个调用步步关键,但是在这个小节中主要探讨协程的创建,所以我们只关心1流程。

createCoroutineUnintercepted()方法追踪进去会到IntrinsicsKt.class类,在这里看不到具体实现,因为这是平台相关性,所以需要直接定位到IntrinsicsJvm.class就能找到该方法在JVM虚拟机上的实现。

1.2.4 createCoroutineUnintercepted()

//IntrinsicsJvm.class
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)
    	//1. 进入这里
        create(receiver, probeCompletion)
    else {
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}

由之前对Suspend Lambda的编译分析可知,(suspend () -> T)实际生成的是实例是继承BaseContinuationImpl,所以1调用步骤。而1调用了BaseContinuationImpl.create()方法,此方法在生成的SuspendLambda实例被实现,最终调用到 1.1.1#1流程 具体往下看1.2.4.1小节。

1.2.4.1 create()

//1. 重写了BaseContinuationImpl.create()方法。
    @Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Continuation<Unit> create(Object obj, Continuation<?> continuation) {
        // 直接new了一个自身实例出来返回。
        return new MainActivity$onCreate$1(continuation);
    }
  1. 可以看到这个方法里就直接new了一个MainActivity$onCreate$1类型实例,具体可以参考1.1小节。后续我们称此为续体1。
  2. 这里需要注意到的是,构建是传入了一个continuation变量,这个变量最终赋值给了BaseContinuationImpl.completion成员变量,这里可以参考1.1.4小节。而这个continuation变量就是在1.2.1小节中创建的一个StandaloneCoroutine实例,协程体1。

1.2.4 intercepted()

 public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

此方法是取出协程是下午文中的interceptor,用于协程调度。为了简化例子,在这个例子中使用的是Dispatcher.Default,而interceptContinuation会创建一个DispatchedContinuation对象。

1.2.5 resumeCancellableWith

@InternalCoroutinesApi
public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    //如是是调度器续体,那么将会进入resumeCancellableWith(), 调度流程。
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    //进入这个分支,详见1.1.4小节BaseContinuationImpl.java 实现
    else -> resumeWith(result)
}

此时续体已经被包装成一个DispatchedContinuation对象,但是本小节为了简化流程,所以看作是调用了下面else分支。这块协程线程调度的逻辑将在第二篇文章分析。

可以看到最终他是调用到了resumeWith(),而此方法就是前文1.1.4小节提到状态机启动的入口。流程上可以看下面1.2.5.1小节。

1.2.5.1 resumeWith()

public final override fun resumeWith(result: Result<Any?>) {
      	...
        while (true) {
           	...
            with(current) {
            	...
                val outcome: Result<Any?> =
				...
                // 调用协程续体代码,流程见1.2.6.2
                val outcome = invokeSuspend(param)
               ...
            }
        }
    }

简化了代码,只保留了主流程上的关键代码。

  1. 此方法是续体1的父类的默认实现。

  2. 可以看到调用了invokeSuspend()方法,此方法是由编译器在生成续体1时,自动生成实现的方法。详见1.2.5.2。

1.2.5.2 invokeSuspend()

public final Object invokeSuspend(Object obj) {
        IntrinsicsKt.getCOROUTINE_SUSPENDED();
        switch (this.label) {
            case 0:
                ResultKt.throwOnFailure(obj);
                //协程的实际逻辑代码
                Log.d(MainActivity.TAG, "onCreate: print in launch.");
                return Unit.INSTANCE;
            default:
                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }
    }

在自动生成的方法体里,有我们写的协程内容代码,所以调用此方法便会触发我们希望协程执行的逻辑代码。

1.2.5 总结

  1. 通过launch启动协程时,会先创建一个StandaloneCoroutine续体作为结束回调,最终传给生成的SuspendLambda实例作为其成员变量。
  2. createCoroutineUnintercepted()方法的实现其实是在IntrinsicsJvm.class文件里,他调用了生成的实例的create()方法,最终new出了一个MainActivity$onCreate$1类型实例。
  3. 协程代码的执行触发流程:BaseContinuationImpl.resumeWith()->BaseContinuationImpl.resume()->MainActivity$onCreate$1.invokeSuspend()

二、协程挂起/恢复

在前面小节中,我们用一个简单的协程例子,剖析了协程启动过程的中细节,其中要点包括:

  1. SuspendLambda的自动生成。
  2. 新建外层续体作为completion回调。
  3. 触发协程业务逻辑代码的流程分析。

在本小节中,我们将要探讨的是协程的挂起/恢复机制。协程的挂起/恢复机制是协程的一大特点,它使得本来异步编程可以像同步编程方式一样直观明了,而不用陷入层层回调中。为了能够分析此流程,我们需要稍微复杂一点的协程代码例子:

2.1 协程-挂起

class MainActivity : ComponentActivity() {

    val TAG = "MainActivity"

    @OptIn(DelicateCoroutinesApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        GlobalScope.launch {
            Log.d(TAG, "onCreate: log in continue.")
            //1. 详见2.1.1,调用job1挂起函数,
            job1()
            Log.d(TAG, "onCreate: ready to run job2.")
            //2. 详见2.1.2,调用job2
            job2()
        }
    }

    suspend fun job1() {
        delay(1000)
        Log.d(TAG, "job1: finish.")
    }

    suspend fun job2() {
        Log.d(TAG, "job2: finish.")
    }
}

在开启本小节的分析前,先简单扼要的阐述下协程神秘的挂起/恢复机制为什么可以化异步为同步:

  1. 在协程内,当执行到一个job1 挂起函数时,会将外层的续体Continuation作为参数传入,修改状态机的label标志位。
  2. 并挂起函数执行异步业务逻辑并同步的返回一个SUSPEND标志位,此时外层协程方法会被return。
  3. 当job1函数异步流程执行结束,会调用Continuation.resume()方法,通知外层续体继续执行,由于此时状态机的label已经被修改,所以会接着执行挂起函数后面的逻辑。

在这个例子,启动了一个协程,在这个协程里,我们分别调用了suspend 函数job1和job2。从第一小节的分析我们可知,此协程代码会被编译器进一步处理。

2.1.1 挂起:SuspendLambda 匿名内部类

protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, null, null, (Function2)new SuspendLambda(null)
    {
    //1. 状态机标志位
      int label;
	...

	  //2. 协程实际业务逻辑代码。
      public final Object invokeSuspend(Object paramObject)
      {
        //注意这里localObject=SUSPEND标志位
        Object localObject = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        1 local1;
        switch (this.label)
        {
        default:
          throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        case 2:
          ResultKt.throwOnFailure(paramObject);
          break;
        case 1:
          local1 = this;
          ResultKt.throwOnFailure(paramObject);
          break;
        case 0:
          ResultKt.throwOnFailure(paramObject);
          // 3. local1 为外层续体。
          local1 = this;
          Log.d(local1.this$0.getTAG(), "onCreate: log in continue.");
          MainActivity localMainActivity1 = local1.this$0;
          //将外层续体赋值给localContinuation2
          Continuation localContinuation1 = (Continuation)local1;
          // 将状态机标志位置为1
          local1.label = 1;
          //4. 调用job1函数,同时将外层续体作为参数传入。
          if (localMainActivity1.job1(localContinuation1) != localObject)
            break;
          return localObject;
        }
        Log.d(local1.this$0.getTAG(), "onCreate: ready to run job2.");
        MainActivity localMainActivity2 = local1.this$0;
        Continuation localContinuation2 = (Continuation)local1;
        local1.label = 2;
        if (localMainActivity2.job2(localContinuation2) == localObject)
          return localObject;
        return Unit.INSTANCE;
      }
    }
    , 3, null);
  }

通过分析反编译出来的协程代码得知,写在协程里面的业务代码是基于状态机来执行的,当来到要调用job1函数时:

  1. 此时label==0,进入case 0。
  2. 将label置为1。
  3. 将续体1也就是SuspendLambda作为参数传入job1。这里注意到在我们写的源码中job函数是没有任何入参的,此时调用却需要这个入参,说明job函数在编译时被增加了这个类型入参。

这也是suspend关键字修饰的函数为什么只能在协程中调用的原因,因为它需要一个续体作为参数。

接下来继续步进流程job1()函数。

2.1.2 挂起:job1()

public final Object job1(Continuation<? super Unit> paramContinuation)
  {
    ...
    // 1. 将外层续体1作为参数,新建一个协程实现类。
    job1.1 local11 = new ContinuationImpl(paramContinuation)
    {
      Object L$0;
      int label;

      public final Object invokeSuspend(Object paramObject)
      {
        this.result = paramObject;
        this.label = (0x80000000 | this.label);
        //注意这里调用回job1()函数
        return MainActivity.this.job1((Continuation)this);
      }
    };
    label46: job1.1 local12 = local11;
    Object localObject1 = local12.result;
    // 记录标志位
    Object localObject2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    MainActivity localMainActivity;
    switch (local12.label)
    {
    default:
      throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    case 1:
      localMainActivity = (MainActivity)local12.L$0;
      ResultKt.throwOnFailure(localObject1);
      break;
    case 0:
      ResultKt.throwOnFailure(localObject1);
      long l = LiveLiterals.MainActivityKt.INSTANCE.Long$arg-0$call-delay$fun-job1$class-MainActivity();
      local12.L$0 = this;
      //2. 更新label
      local12.label = 1;
      //3. 调用delay函数
      if (DelayKt.delay(l, local12) == localObject2)
        //4. 等于SUSPEND标志位,直接返回SUSPEND标志位
        return localObject2;
      localMainActivity = this;
    }
    Log.d(localMainActivity.TAG, "job1: finish.");
    return Unit.INSTANCE;
  }

在job1()函数中:

  1. 先将外层续体1作为参数生成一个续体实现类,我们称之为job1续体,并复制给local11
  2. 进入case0,并更新label=1。
  3. 调用delay函数,这个函数是一个延时函数,这里并不打算深究进去,但是可以猜想到的是:
    1. 在delay函数里面会开启一个定时任务并先返回一个COROUTINE_SUSPENDED标志位。
    2. 时间到时,定时任务触发时,调用local12.resumeWith()切换到job1续体实现类代码,
    3. resumeWith经过调用进入到local12.inovkeSuspen()函数。
  1. 在流程3中,由于调用了delay函数,其返回SUPEND标志位,判断成立,函数返回。所以流程又回到了2.1.1#4步骤:此时又在续体1中判定了一次:COROUTINE_SUSPENDED != COROUTINE_SUSPENDED判定不成功,所以returninovkeSuspend()函数。

  2. 此时便回到我们调用invokSuspend()函数的地方,通过第一小节可以知道是BaContinuationImpl.resumtWith()详细见 2.2。

2.1.3 挂起:job2()

  public final Object job2(Continuation<? super Unit> paramContinuation)
  {
    Log.d(this.TAG, LiveLiterals.MainActivityKt.INSTANCE.String$arg-1$call-d$fun-job2$class-MainActivity());
    return Unit.INSTANCE;
  }

job2 函数虽然被suspend修饰,但是内部并没用调用任何挂起函数,内部也就不会返回SUSPEND标志位,所以和普通函数一致。

2.2 挂起:resumeWith()

public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
           	...
            with(current) {
                 // fail fast when trying to resume continuation without completion
                val completion = completion!!
                 // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        // 此方法返回COROUTINE_SUSPENDED 标志位
                        val outcome = invokeSuspend(param)
                        //此时判断成功return。
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

此时返回挂起标志位,所以会将此方法直接return掉,至此协程实现了挂起。

但是挂起后的协程是怎么被恢复的呢?这个需要接着2.1.2的delay函数分析流程。

2.2 协程-恢复

2.2.1 恢复:job1()

public final Object job1(Continuation<? super Unit> paramContinuation)
  {
    ...
    job1.1 local11 = new ContinuationImpl(paramContinuation)
    {
      Object L$0;
      int label;

      public final Object invokeSuspend(Object paramObject)
      {
        this.result = paramObject;
        this.label = (0x80000000 | this.label);
        //1. 调用回job1()函数
        return MainActivity.this.job1((Continuation)this);
      }
    };
    ...
    switch (local12.label)
    {
   	...
    case 1:
        //此时进入这个分支
      localMainActivity = (MainActivity)local12.L$0;
      ResultKt.throwOnFailure(localObject1);
      break;
    case 0:
      ,..
    }
    Log.d(localMainActivity.TAG, "job1: finish.");
    return Unit.INSTANCE;
  }

在2.1.2#3步骤分析中,我们知晓delay函数,在定时时间到时,调用job1续体的resume()函数通知恢复,所以会回到job1续体的invokeSuspend方法,此时:

  1. 此时label==1,所以进入case1,随后return Unit.INSTANCE
  2. 根据之前的分析,此时会直接返回到local11.resumeWith() 详见2.2.2

2.2.2 恢复 Job1.resumeWith()

public final override fun resumeWith(result: Result<Any?>) {
        ...
        while (true) {
           	...
            with(current) {
                 // fail fast when trying to resume continuation without completion
                val completion = completion!!
                 // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        //1. 此方法返回Unit.INSTANCE 标志位
                        val outcome = invokeSuspend(param)。
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    //2. completion 是DispatchedCoroutine类型,所以进入此流程;(TODO: 这里待研究)
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
  1. 从2.3 可以知道返回的是Unit.INSTANCE,所以进入到下面的if判断。
  2. completion就是job函数传入进来的,也就是我们的外层的协程续体1,实际为是DispatchedCoroutine类型,调用了completion.resumeWith(outcome)。就会进行协程的线程切换,并且最终调用到外层实际协程体的SuspendLambda.resumeWith()->SuspendLambda.invokSuspended()方法。

这里为什么是DispatchedCoroutine,因为这里涉及到了协程的线程调度,所以需要调用resumeWith进行线程调度,而BaseContinuationImpl就不用,直接相当于同步代码往下执行了。

2.2.3 恢复:SuspendLambda.invokSuspended()

 public final Object invokeSuspend(Object paramObject)
      {
        //注意这里localObject=SUSPEND标志位
        Object localObject = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        1 local1;
        switch (this.label)
        {
    	...
        case 1:
          local1 = this;
          ResultKt.throwOnFailure(paramObject);
          break;
        }
        //进入此流程
        Log.d(local1.this$0.getTAG(), LiveLiterals.MainActivityKt.INSTANCE.String$arg-1$call-d-1$fun-$anonymous$$arg-2$call-launch$fun-onCreate$class-MainActivity());
        MainActivity localMainActivity2 = local1.this$0;
        Continuation localContinuation2 = (Continuation)local1;
        local1.label = 2;
        if (localMainActivity2.job2(localContinuation2) == localObject)
          return localObject;
        return Unit.INSTANCE;
      }

再次回到这个函数时:

  1. label因为之前置为1了,所以进入case1流程
  2. 先置label=2
  3. 和调用job1时一样, 将外层续体作为参数传入job2。

自此,主协程便又恢复了运行,接着去执行job2的代码了。后续的逻辑如果由挂起就还是和job1的流程一致,如果job2里面没调用挂起函数,那么也就没有挂起标志,所以直接同步执行后结束主协程的业务逻辑。

2.3 总结

  1. suspend 修饰的函数会经过CPS处理后,为函数增加一个Continuation的入参,而这个入参的值便是调用此函数的父协程。

  2. 协程的挂起在于:执行到异步步耗时机制时,它提供了一个SUSPEND标志以供返回,协程的状态机检测到这个标识位后便return了这个函数,此为挂起。

  3. 协程的恢复在于:当耗时或者异步任务结束时,可以调用Continuation.resumeWith()恢复协程,最终此函数的协程走完,便会调用completion.resumeWith(outcome),这个completion便是之前传入的父协程。自此父协程恢复状态机的运转,接着执行后续的代码。

  4. 协程的挂起不是当前协程一直阻塞在挂起函数的执行,而是直接return掉,等后续挂起函数执行完成后在通过回调的方式通知父协程继续执行后需的代码。

三、协程中的概念说明

在上文中我们经常提到协程,续体。本小节针对这2个概念在协程源码中的代表类型进行简要的说明。

3.1 协程体

/**
 * Abstract base class for implementation of coroutines in coroutine builders.
 *
 * This class implements completion [Continuation], [Job], and [CoroutineScope] interfaces.
 * It stores the result of continuation in the state of the job.
 * This coroutine waits for children coroutines to finish before completing and
 * fails through an intermediate _failing_ state.
 *
 * The following methods are available for override:
 *
 * * [onStart] is invoked when the coroutine was created in non-active state and is being [started][Job.start].
 * * [onCancelling] is invoked as soon as the coroutine starts being cancelled for any reason (or completes).
 * * [onCompleted] is invoked when the coroutine completes with a value.
 * * [onCancelled] in invoked when the coroutine completes with an exception (cancelled).
 *
 * @param parentContext the context of the parent coroutine.
 * @param initParentJob specifies whether the parent-child relationship should be instantiated directly
 *               in `AbstractCoroutine` constructor. If set to `false`, it's the responsibility of the child class
 *               to invoke [initParentJob] manually.
 * @param active when `true` (by default), the coroutine is created in the _active_ state, otherwise it is created in the _new_ state.
 *               See [Job] for details.
 *
 * @suppress **This an internal API and should not be used from general code.**
 */
@InternalCoroutinesApi
public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    ...
}

AbstractCoroutine类型代表的是一个协程,它的注释比较重要,直接能说明此类的作用:

  1. 它是一个协程体的基本实现的,抽象类。
  2. 它实现了[Continuation], [Job] [CoroutineScope]接口,说明它有如下属性:
    1. [Continuation]:是一个续体对象,可以通过回调resume恢复此协程。
    2. [Job] :是一个协程作业对象,能够取消和获取作业状态。
    3. [CoroutineScope]:是一个协程域对象,可以统一管理此协程域里的子协程。

3.2 续体

/**
 * Interface representing a continuation after a suspension point that returns a value of type `T`.
 */
@SinceKotlin("1.3")
public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

Continuation类型代表一个在挂起后,协程代码要继续往下执行的续体 (代码接下执行的续点) 。关键的有:

  • context:此续体执行的上下文环境。
  • resumeWith:欲恢复此续体继续执行时调用此函数。