协程是如何实现的

Continuation-Passing Style,续体传递风格

CPS是一种函数式编程风格。在普通函数的参数中增加一个Continuation参数。

例如定义一个普通函数并在main函数中打印:

fun normalFunc(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    println(normalFunc(1, 2))
}
复制代码

如果把这个例子改成CPS函数就是:

interface CPSContinuation {
    fun plus(result: Int)
}

fun cpsFunc(a: Int, b: Int, continuation: CPSContinuation) {
    continuation.plus(a + b)
}

fun main() {
    cpsFunc(1, 2, object : CPSContinuation {
        override fun plus(result: Int) {
            println(result)
        }
    })
}
复制代码

续体传递风格的编程简单来说就是,在常规函数中新增一个参数continuation,这个continuation的性质类似于一个lambda对象,从代码上看:

  • CPS函数相较于传统函数,多了一个continuation参数。
  • continuation内部定义了一个方法(名字随意,不是自动生成),这个方法将传统方法的返回值作为参数,方法内部实现,需要在CPS函数传的continuation对象中实现。
  • continuation对象的内部方法将调用常规方法的逻辑做了实现。

如果换成Java风格,无需使用匿名对象,直接用block即可。即函数的结果变成了通过block来传递,协程就是通过在CPS的Continuation回调里结合状态机流转,来实现协程挂起-恢复的功能的。

在协程中,编译器会将挂起函数转换为CPS函数:增加一个continuation参数,并改变return类型为 Any?

Suspend 挂起

协程构造器接收一个suspend关键字修饰的闭包,同样的,suspend修饰的方法或lambda表达式,都只能在suspend的代码块范围内使用,准确的来说,suspend修饰的作用范围,和普通函数是相互区分,不相容的。在代码中,一般suspend关键字能够使用的情景有两种,挂起函数和挂起lambda表达式:

suspend () -> Unit

suspend fun function()
复制代码

这里举例说明suspend的作用,例如以下代码:

suspend fun dummy() {}

suspend fun main() {
    val lambda: suspend () -> Unit = {
       println(1)
       dummy()
       println(2)
       dummy()
       println(3)
    }
    lambda()
}
复制代码

这是一个没有挂起操作的协程,它会以此打印1、2。我们在dumy中添加挂起操作

var c: Continuation<Unit>? = null

suspend fun dummy() = suspendCoroutine<Unit> { continuation ->
    println("Suspended")
    c = continuation
}
复制代码

重新运行,打印结果为:

1
Suspended
复制代码

此时main函数一致被挂起,导致阻塞。如果我们需要不阻塞main函数的场景,需要将其改造成普通函数:

// 首先定义一个普通函数到挂起函数的桥接方法
fun builder(c: suspend () -> Unit) {
    c.startCoroutine(object: Continuation<Unit> {
        override val context = EmptyCoroutineContext
        override fun resumeWith(result: Result<Unit>) {
            result.getOrThrow()
        }
    })
}
复制代码

然后将main函数改造成普通函数:

fun main() {
    val lambda: suspend () -> Unit = {
      	println(1)
        dummy()
        println(2)
        dummy()
        println(3)
    }
    builder {
        lambda()
    }
}
复制代码

打印结果为:

1
Suspended
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
复制代码

可以看出,挂起后,main函数执行结束了。那么,如果我们要恢复这个挂起函数呢?

通过c?.resume(Unit)即可实现,需要注意的是,当挂起函数如果不处于挂起状态调用这个方法,会抛出IllegalStateException: Already resumed

从这个例子可以得出结论,挂起函数和普通函数之间,需要创建一个顶级桥接协程,协程在普通函数中运行,而挂起函数在协程作用域范围内运行。

Suspend Lambda

如上面例子中的lambda:

    val lambda: suspend () -> Unit = {
      	println(1)
        dummy()
        println(2)
        dummy()
        println(3)
    }
复制代码

反编译为字节码,存在以下内容:

// 挂起lambda
final class com/chunyu/coroutines/CreateTestKt$main$lambda$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function1
复制代码

编译器在编译时会为挂起lambda生成一个内部类,这个东西仅在字节码中能够看见,反编译为Java就消失了。

它的继承关系是 继承自SuspendLambdaFunction1。这两个东西,是由SuspendFuncation生成的。

// 为了区分挂起函数类型和普通函数类型,所有挂起函数类型都应该实现这个接口
internal interface SuspendFunction

// suspend lambda 继承自此类
internal abstract class SuspendLambda(
    public override val arity: Int,
    completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction
复制代码

挂起lambda有一个指定的挂起函数类型: SuspendFuncation { N },N是lambda表达式的参数。它只在编译期间存在,并转换为Function{N+1}SuspendLambda。因为 SuspendFuncation { N }在运行时不存在,它的作用是用来区分普通函数类型和挂起函数类型。

它可以用于 is SuspendFuncation { N }as SuspendFuncation { N }表达式。例如:

      boolean var2 = lambda instanceof SuspendFunction ? TypeIntrinsics.isFunctionOfArity(lambda, 1) : false;
      System.out.print(var2);
复制代码

SuspendLambda类中的arity,就是参数数量 + 1。

kotlin.suspend

public inline fun <R> suspend(noinline block: suspend () -> R): suspend () -> R = block
复制代码

通常在普通的函数或lambda中,不能直接使用suspend关键字,且lambda没有参数的情况下,可以使用这个方法创建一个可挂起的block。

Suspend函数

将上面的lambda改造成函数:

    suspend fun lambdaFunc() {
      	println(1)
        dummy()
        println(2)
        dummy()
        println(3)
    }
复制代码

字节码中,也存在一个内部类:

final class com/chunyu/coroutines/SuspendTestKt$lambdaFunc1$1 extends kotlin/coroutines/jvm/internal/ContinuationImpl
复制代码

与挂起lambda不同的是他们的继承关系。虽然两者的继承关系不同,但是继承关系中都有ContinuationImpl

这个类与CPS有关。在CPS中,每个挂起函数都会被编译器增加一个continuation参数,并且更改返回类型为 Any?。而无论是挂起lambda还是挂起函数,都需要增加一个continuation参数,这个参数的类型,自然需要通过编译器自动生成。就是字节码中提到的内部类。这个内部类中,包含了协程中最重要的状态变化逻辑,称之为状态机。

状态机

编译器通过使用状态机将顺序代码变成可挂起的。挂起调用将代码分为不同的状态。挂起调用和状态之间是一对一的关系:每次挂起调用会获取一个状态,每个状态只有一次挂起调用。

状态以挂起调用为结束。编译器会将挂起调用之前的所有代码存放到当前状态中,最后一次调用之后的代码,也会存放到一个单独的状态中。例如:

dummy() // 假设这里有挂起操作
println(1)
dummy()
println(2)
复制代码

编译器按照以下方式拆分代码:

==========
dummy()
----------
println(1)
dummy()
----------
println(2)
==========
复制代码

函数的边界以 === 表示,状态的边界以 --- 表示。编译器在分割这个函数后会生成以下代码:

val $result: Any? = null
when (this.label) {
    0 -> {
        this.label = 1
        $result = dummy(this)
        if ($result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        goto 1
    }
    1 -> {
        println(1)
        this.label = 2
        $result = dummy(this)
        if ($result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        goto 2
    }
    2 -> {
        println(2)
        return Unit
    }
    else -> {
        throw IllegalStateException("call to 'resume' before 'invoke' with coroutine")
    }
}
复制代码

然后,编译器会将这个状态机代码放在 invokeSuspend 函数中。在挂起函数和挂起方法的章节我们提到过,挂起lambda继承自SuspendLamda,而挂起函数会为状态机生成一个ContinuationImpl的子类,它们都继承自BaseContinuationImpl:

abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    public final override fun resumeWith(result: Result<Any?>)
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?
    protected open fun releaseIntercepted()
    public open fun create(completion: Continuation<*>): Continuation<Unit>
    public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit>
    public override fun toString(): String
    public override val callerFrame: CoroutineStackFrame?
    public override fun getStackTraceElement(): StackTraceElement?
}
复制代码

这里的 invokeSuspend 函数就是存放状态机代码的地方。

举个例子,例如以下代码:

suspend fun dummy() {}

suspend fun main() {
    val lambda: suspend () -> Unit = {
        dummy()
        println(1)
        dummy()
        println(2)
        dummy()
        println(3)
    }
    lambda()
}
复制代码

反编译为Java,一步一步分析,首先是编译器生成了两个类SuspendTestKt$$$mainSuspendTestKt,前者是main函数生成的类,后者是其他方法所在的类。

final class SuspendTestKt$$$main extends Lambda implements Function1 {

  private final String[] mArgs;

   SuspendTestKt$$$main(String[] args) {
      super(1);
      this.mArgs = args;
   }

   public final Object invoke(Object obj) {
      return SuspendTestKt.main((Continuation)obj);
   }
}
复制代码

main函数调用时,指向了SuspendTestKt的main方法:

public final class SuspendTestKt {
   @Nullable
   public static final Object main(@NotNull Continuation $completion) {
   		Function1 lambda = (Function1)(new Function1((Continuation)null) { 
      		// ...
      });   
      Object result = lambda.invoke($completion);
      return result == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? result : Unit.INSTANCE;
   }
}
复制代码

可以看出,调用了lambda对象的invoke方法获取一个result,再根据这个result返回COROUTINE_SUSPENDED或Unit。

这个lambda对象是:

        Function1 lambda = (Function1)(new Function1((Continuation)null) {
            int label;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
                byte text;
                @BlockTag1: {
                    Object result;
                    @BlockTag2: {
                        result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                        switch(this.label) {
                            case 0:
                                ResultKt.throwOnFailure($result);
                                this.label = 1;
                                if (SuspendTestKt.dummy(this) == result) {
                                    return result;
                                }
                                break;
                            case 1:
                                ResultKt.throwOnFailure($result);
                                break;
                            case 2:
                                ResultKt.throwOnFailure($result);
                                break @BlockTag2;
                            case 3:
                                ResultKt.throwOnFailure($result);
                                break @BlockTag1;
                            default:
                                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                        }

                        text = 1;
                        System.out.println(text);
                        this.label = 2;
                        if (SuspendTestKt.dummy(this) == result) {
                            return result;
                        }
                    }

                    text = 2;
                    System.out.println(text);
                    this.label = 3;
                    if (SuspendTestKt.dummy(this) == result) {
                        return result;
                    }
                }
                text = 3;
                System.out.println(text);
                return Unit.INSTANCE;
            }

            @NotNull
            public final Continuation create(@NotNull Continuation completion) {
                Intrinsics.checkNotNullParameter(completion, "completion");
                Function1 funcation = new <anonymous constructor>(completion);
                return funcation;
            }

            public final Object invoke(Object object) {
                return ((<undefinedtype>)this.create((Continuation)object)).invokeSuspend(Unit.INSTANCE);
            }
        });
复制代码

这个对象有invokeSuspend方法、create方法、invoke方法和label字段,很明显这个和BaseContinuationImpl的定义基本一致。这个就是挂起lambda经过编译器处理后生成的ContinuationImpl的实现。

逐个方法分析,首先是invoke:

            public final Object invoke(Object object) {
                return ((<undefinedtype>)this.create((Continuation)object)).invokeSuspend(Unit.INSTANCE);
            }
复制代码

invoke依次调用create和invokeSuspend,create代码:

            @NotNull
            public final Continuation create(@NotNull Continuation completion) {
                Intrinsics.checkNotNullParameter(completion, "completion");
                Function1 funcation = new <anonymous constructor>(completion);
                return funcation;
            }
复制代码

生成一个匿名Function1对象并返回,最后是invokeSuspend:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
    byte text;
    @BlockTag1: {
        Object result;
        @BlockTag2: {
            result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(this.label) {
                case 0:
                    ResultKt.throwOnFailure($result);
                    this.label = 1;
                    if (SuspendTestKt.dummy(this) == result) {
                        return result;
                    }
                    break;
                case 1:
                    ResultKt.throwOnFailure($result);
                    break;
                case 2:
                    ResultKt.throwOnFailure($result);
                    break @BlockTag2;
                case 3:
                    ResultKt.throwOnFailure($result);
                    break @BlockTag1;
                default:
                    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            text = 1;
            System.out.println(text);
            this.label = 2;
            if (SuspendTestKt.dummy(this) == result) {
                return result;
            }
        }

        text = 2;
        System.out.println(text);
        this.label = 3;
        if (SuspendTestKt.dummy(this) == result) {
            return result;
        }
    }
    text = 3;
    System.out.println(text);
    return Unit.INSTANCE;
}
复制代码

这里先省略代码块BlockTag1中的细节:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
    byte text;
    @BlockTag1: {
        // .. 
    }
    text = 3;
    System.out.println(text);
    return Unit.INSTANCE;
}
复制代码

先执行代码块BlockTag1,BlockTag1执行完成后,text = 3,然后打印,并返回 Unit。对比我们的kotlin代码:

suspend fun main() {
    val lambda: suspend () -> Unit = {
        // =============
        // state 1
        dummy()
      	// -------------
				// state 2
        println(1)
        dummy()
        // -------------
      	// state 3
        println(2)
        dummy()
        // -------------
      	// state 4
        println(3)
        // =============
    }
    lambda()
}
复制代码

通过 === 和 --- 将lambda分为四个状态,BlockTag1后续的代码,等同于状态4。

再看看BlockTag1的伪代码:

    @BlockTag1: {
        Object result;
        @BlockTag2 { 
           // ... 
        }
        text = 2;
        System.out.println(text);
        this.label = 3;
        if (SuspendTestKt.dummy(this) == result) {
            return result;
        }
    }
复制代码

这里text = 2,同时label置为了3,label的作用后续会讲到,BlockTag1中,BlockTag2的后续代码可以理解为状态3。

继续分析BlockTag2:

@BlockTag2: {
    result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    switch(this.label) {
        case 0:
            ResultKt.throwOnFailure($result);
            this.label = 1;
            if (SuspendTestKt.dummy(this) == result) {
                return result;
            }
            break;
        case 1:
            ResultKt.throwOnFailure($result);
            break;
        case 2:
            ResultKt.throwOnFailure($result);
            break @BlockTag2;
        case 3:
            ResultKt.throwOnFailure($result);
            break @BlockTag1;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

    text = 1;
    System.out.println(text);
    this.label = 2;
    if (SuspendTestKt.dummy(this) == result) {
        return result;
    }
}
复制代码

BlockTag2中,switch语句后续部分的text = 1,label赋值2,对应状态2。这个时候不难发现,如果dumy函数没有挂起操作,代码按顺序执行,不会走到if (SuspendTestKt.dummy(this) == result)中,这个时候会继续执行代码块外部的代码,也就是说this.label = 2;及其后执行顺序的伪代码:

				this.label = 2;
				text = 2;
        System.out.println(text);
        this.label = 3;
        text = 3;
        System.out.println(text);
        return Unit.INSTANCE;
复制代码

label初始化时默认为0,所以会先走switch语句中的case 0分支:

    switch(this.label) {
        case 0:
            ResultKt.throwOnFailure($result);
            this.label = 1;
            if (SuspendTestKt.dummy(this) == result) {
                return result;
            }
            break;
        case 1:
            ResultKt.throwOnFailure($result);
            break;
        case 2:
            ResultKt.throwOnFailure($result);
            break @BlockTag2;
        case 3:
            ResultKt.throwOnFailure($result);
            break @BlockTag1;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }
复制代码

Case 0分支什么也没有,对应了dumy的空实现,所以是状态1。

另外,这个switch语句按顺序会在BlockTag1最先执行,目的是直接根据label分发状态。

那么,如果dumy内存在挂起操作呢?把dumy函数内增加一个delay操作:

suspend fun dummy() {
    delay(10000)
}
复制代码

对应反编译结果:

   @Nullable
   public static final Object dummy(@NotNull Continuation $completion) {
      Object result = DelayKt.delay(10000L, $completion);
      return result == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? result : Unit.INSTANCE;
   }
复制代码

如果存在挂起操作,函数会返回COROUTINE_SUSPENDED,在最深处的代码块中存在:

						result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
						
						if (SuspendTestKt.dummy(this) == result) {
                return result;
            }
复制代码

导致整个invokeSuspend函数直接return,这就是挂起导致的协程阻塞。

协程启动流程

以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
}
复制代码

newCoroutineContext方法中,拿到了外部协程上下文然后再拼接自身的协程上下文,从而实现了继承上下文的能力。然后创建了一个协程对象。

这里的LazyStandaloneCoroutine继承自StandaloneCoroutine

他们都继承自AbstractCoroutine,继承关系如下:

从这张继承关系图可以看出AbstractCoroutine实现了JobSupport类和JobContinuation<T>以及CoroutineScope三个接口。

  • Job接口返回的是协程任务,用来做返回值类型处理。
  • CoroutineScope是协程作用域接口,定义了能够获取协程上下文的能力。
  • JobSupport是Job的具体实现。

那么Continuation<T>是个什么东西?

Continuation

public interface Continuation<in T> {
    /**
     * 与此Continuation相关的协程上下文
     */
    public val context: CoroutineContext

    /**
     * 恢复执行相应的协程,传递一个成功或失败的[result]作为最后一个暂停点的返回值
     */
    public fun resumeWith(result: Result<T>)
}
复制代码

这个interface就是Kotlin协程使用的CPS中的continuation的定义。

协程的创建

继续分析launch方法中的代码,以StandaloneCoroutine对象为例,它最后调用到了coroutine.start(start, coroutine, block):

    // coroutine.start(start, coroutine, block)
		/*
    * 通过给定的代码块和启动策略来启动协程。此函数最多应在此协程上调用一次。
    */
		public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        startAbstractCoroutine(start, receiver, this, block)
    }
复制代码

这里的receiver类型为StandaloneCoroutine;通过备注可以看出,因CoroutineStart的值不同,会走不同的实现方法。先查看startAbstractCoroutine的内部实现:

internal actual inline fun <T, R> startAbstractCoroutine(
    start: CoroutineStart,
    receiver: R,
    coroutine: AbstractCoroutine<T>,
    noinline block: suspend R.() -> T
) {
    startCoroutineImpl(start, receiver, coroutine, null, block)
}
复制代码

跟进到startCoroutineImpl方法内部:

internal fun <T, R> startCoroutineImpl(
    start: CoroutineStart,
    receiver: R,
    completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)?,
    block: suspend R.() -> T
) = when (start) {
    CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion, onCancellation)
    CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
    CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
    CoroutineStart.LAZY -> Unit // will start lazily
}
复制代码

根据CoroutineStart的不同类型做了不同的逻辑:

  • startCoroutineCancellable
  • startCoroutine
  • startCoroutineUndispatched
  • LAZY return Unit

依次查看三个方法的实现:

startCoroutine

public fun <R, T> (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}
复制代码

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)
    }
复制代码

startCoroutineUndispatched

internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
    startDirect(completion) { actualCompletion ->
        withCoroutineContext(completion.context, null) {
            startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
        }
    }
}
复制代码

除了startCoroutineUndispatched方法,其他两个函数的内部逻辑基本一致:再调用该方法返回对象的intercepted()方法拦截协程,最后通过不同的resume方法来启动协程。启动协程分三个步骤:

  1. 通过createCoroutineUnintercepted方法创建Continuation
  2. 调用intercepted()方法
  3. 调用不同的resume唤起协程

创建Continuation

createCoroutineUnintercepted

该函数在不同的平台有不同的实现,而且有两种泛型参数的实现,后者的区别是接收类型和返回类型,都是用来创建一个未被拦截的协程。

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)
        }
}
复制代码

js平台的实现:

public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
    completion: Continuation<T>
): Continuation<Unit> =
    createCoroutineFromSuspendFunction(completion) {
        val a = this.asDynamic()
        if (jsTypeOf(a) == "function") a(completion)
        else this.invokeSuspendSuperType(completion)
    }
复制代码

内部会根据Continuation的泛型类型来判断执行不同的实现方法,createcreateCoroutineFromSuspendFunction。先看看这里的BaseContinuationImpl的继承关系:

   +------------+
   |Continuation|
   +------+-----+
          ^
          |
+---------+----------+
|BaseContinuationImpl+<---------------+
+---------+----------+                |
          ^                           |
          |                           |
  +-------+--------+    +-------------+------------+
  |ContinuationImpl|    |RestrictedContinuationImpl|
  +-------+--------+    +-------------+------------+
          ^                           ^
          |                           |
    +-----+-------+       +-----------+-----------+
    |SuspendLambda|       |RestrictedSuspendLambda|
    +-------------+       +-----------------------+
复制代码

可以从这里看出,Continuation接口是顶级接口,本质上,它也是协程机制的核心,协程机制使用CPS,每个挂起函数和lambda都接收一个额外的Continuation参数。 每个编译器生成的continuation都继承自BaseContinuationImpl:

abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    public final override fun resumeWith(result: Result<Any?>)
    protected abstract fun invokeSuspend(result: Result<Any?>): Any?
    protected open fun releaseIntercepted()
    public open fun create(completion: Continuation<*>): Continuation<Unit>
    public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit>
    public override fun toString(): String
    public override val callerFrame: CoroutineStackFrame?
    public override fun getStackTraceElement(): StackTraceElement?
}
复制代码

这里的resumeWith是final的,但是BaseContinuationImpl提供了invokeSuspend函数,resumeWith做了以下事情:

  1. 用户调用resumeWith,它内部会调用ivnokeSuspend。通过result恢复挂起的协程。
  2. 当协程执行结束,会调用completion返回给外部调用者。
  3. 将捕获的异常封装在result中返回给调用者。

编译器会自动生成一个参数或没有参数的create方法,供createCoroutineUnintercepted调用。

ContinuationImpl增加了interceptedintercepted()函数,供后续调用。

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)

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

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

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

    protected override fun releaseIntercepted() {
        val intercepted = intercepted
        if (intercepted != null && intercepted !== this) {
            context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
        }
        this.intercepted = CompletedContinuation // just in case
    }
}
复制代码

一般情况下,因为函数限定了泛型为(suspend () -> T)的拓展函数,所以这里的this一般都是BaseContinuationImpl的子类型。

这里的create函数是一个CPS函数,它的作用是将value保存在continuation中,并将continuation提供给外部使用。

public final Continuation create(@Nullable Object value, @NotNull Continuation completion)
复制代码

另一种逻辑是,当this不是BaseContinuationImpl类型时,会走createCoroutineFromSuspendFunction方法:

private inline fun <T> createCoroutineFromSuspendFunction(
        completion: Continuation<T>,
        crossinline block: (Continuation<T>) -> Any?
): Continuation<Unit> {
    val context = completion.context
 	// 当协程尚未启动时label == 0时或label == 1时
    return if (context === EmptyCoroutineContext)
        object : RestrictedContinuationImpl(completion as Continuation<Any?>) {
            // ....
        }
    else
        object : ContinuationImpl(completion as Continuation<Any?>, context) {
            private var label = 0

            override fun invokeSuspend(result: Result<Any?>): Any? =
                    when (label) {
                        0 -> {
                            label = 1
                            result.getOrThrow() // 如果尝试以异常开始,则重新抛出异常(将被 BaseContinuationImpl.resumeWith 捕获
                            block(this) // 运行block, 可能return或者挂起
                        }
                        1 -> {
                            label = 2
                            result.getOrThrow() // block已挂起的结果
                        }
                        else -> error("This coroutine had already completed")
                    }
        }
}
复制代码

首先取出协程上下文对象,如果写成上下文和EmptyCoroutineContext地址是否相同,若相同,返回一个RestrictedContinuationImpl匿名对象。否则,返回一个ContinuationImpl匿名对象,其中的逻辑基本相同。

  • label默认为0
  • label == 0时,将label置为1,result检查是否抛出异常,然后运行协程中的block。这里是开始恢复执行代码。
  • label == 1时,将label置为2,result检查是否抛出异常。这里是协程执行完成。
  • label为其他值,报错,当前协程已运行。

RestrictedContinuationImpl和ContinuationImpl的区别是,RestrictedContinuationImpl要求协程上下文为EmptyCoroutineContext,而且缺少拦截器intercepted的属性和方法:

internal abstract class RestrictedContinuationImpl(
    completion: Continuation<Any?>?
) : BaseContinuationImpl(completion) {
    init {
        completion?.let {
            require(it.context === EmptyCoroutineContext) {
                "Coroutines with restricted suspension must have EmptyCoroutineContext"
            }
        }
    }

    public override val context: CoroutineContext
        get() = EmptyCoroutineContext
}
复制代码

无论哪种方式,最后都会拿到一个Continuation对象,然后进行下一步调用:intercepted()

intercepted

看到这个方法你会怀疑,为什么Continuation中没有定义这个方法还能调用?实际上它是一个拓展方法,查看它的实现:

// 定义实现
public expect fun <T> Continuation<T>.intercepted(): Continuation<T>

// native平台实际实现
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
        (this as? ContinuationImpl)?.intercepted() ?: this
复制代码

从平台实现发现,如果当前对象是ContinuationImpl的子类,才能调用到。刚刚提到过RestrictedContinuationImpl的情况,就是没有intercepted方法,直接返回自身。

ContinuationImpl.intercepted()方法中,从context取出ContinuationInterceptor,然后调用其interceptContinuation方法,再将这个拦截器对象保存起来。

public interface ContinuationInterceptor : CoroutineContext.Element {
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
    // ... 
}
复制代码

ContinuationInterceptor有一个我们比较熟悉的子类CoroutineDispatcher,协程调度器的基类:

public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    public override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        DispatchedContinuation(this, continuation)
    // ...
}
复制代码

实际上,调度器切换线程的操作也是在这里完成的,DispatchedContinuation对象内部:

internal class DispatchedContinuation<in T>(
    @JvmField val dispatcher: CoroutineDispatcher,
    @JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
    // ... 
    override fun resumeWith(result: Result<T>) {
        val context = continuation.context
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_ATOMIC
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_ATOMIC) {
                withCoroutineContext(this.context, countOrElement) {
                    continuation.resumeWith(result)
                }
            }
        }
    }
    
    inline fun resumeUndispatchedWith(result: Result<T>) {
        withContinuationContext(continuation, countOrElement) {
            continuation.resumeWith(result)
        }
    }
    
    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)
                }
            }
        }
    }
}
复制代码

列举的三个方法很明显和下一步操作resume有关。

resume

回顾startCoroutineImpl方法:

    CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion, onCancellation)
    CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
    CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
    CoroutineStart.LAZY -> Unit // will start lazily
复制代码

我们逐个分析startCoroutineCancellable、startCoroutine和startCoroutineUndispatched:

startCoroutineCancellable

最终调用resumeCancellableWith方法:

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)
}
复制代码

当拦截方法intercepted返回DispatchedContinuation时,就会走到刚刚提到过的DispatchedContinuation中的resumeCancellableWith方法:

    inline fun resumeCancellableWith(
        result: Result<T>,
        noinline onCancellation: ((cause: Throwable) -> Unit)?
    ) {
        val state = result.toState(onCancellation)
        if (dispatcher.isDispatchNeeded(context)) {
						// ... 
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled(state)) {
                    resumeUndispatchedWith(result)
                }
            }
        }
    }
复制代码

dispatcher.isDispatchNeeded(context)方法是用来判断是否应该使用dispatch方法来执行协程,若应该返回true,大多数调度器默认返回true;若返回false,则协程应该立即在当前线程中恢复。

获取状态机的状态,如果dispatcher.isDispatchNeeded(context) == true ,则通过调度器的dispatch方法来处理。否则调用executeUnconfined方法。 若不是DispatchedContinuation类型,则直接调用Continuation的resumeWith方法。Continuation的resumeWith方法后续会单独分析。

startCoroutine

当CoroutineStart类型为ATOMIC时,会走这个方法:

public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
复制代码

该方法最后一步调用的是resume:

@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))
复制代码

resume也是Continuation的拓展方法,内部调用是Continuation的resumeWith方法。

startCoroutineUndispatched
internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
    startDirect(completion) { actualCompletion ->
        withCoroutineContext(completion.context, null) {
            startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
        }
    }
}
复制代码

这个方法比较复杂,首先是最外面的startDirect,用于debug追踪处理。然后是withCoroutineContext,这个简单了,用于切换上下文;最后是startCoroutineUninterceptedOrReturn:

public actual inline fun <T> (suspend () -> T).startCoroutineUninterceptedOrReturn(
        completion: Continuation<T>
): Any? = (this as Function1<Continuation<T>, Any?>).invoke(completion)

public actual inline fun <R, T> (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
        receiver: R,
        completion: Continuation<T>
): Any? = (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, completion)
复制代码

这个方法的含义是,将传入的suspend闭包,强转为cps风格的函数,并调用自动生成的Contionuation的invoke方法。

Continuation的resumeWith

Continuation的实现需要从编译生成的代码和普通代码两条路径分析。

  • 在launch的调用流程中,我们创建了一个StandaloneCoroutine对象,它的父类AbstractCoroutine实现了Continuation<T>
  • 在编译器中,会自动将挂起lambda生成为BaseContinuationImpl的实现类,里面定义了resumeWith的实现逻辑。
代码中的继承

AbstractCoroutine的resumeWith:

public abstract class AbstractCoroutine<in T>(
    @JvmField internal val parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    public final override fun resumeWith(result: Result<T>) {
        val state = makeCompletingOnce(result.toState())
        if (state === COMPLETING_WAITING_CHILDREN) return
        afterResume(state)
    }
    
    protected open fun afterResume(state: Any?): Unit = afterCompletion(state)
    
    protected open fun afterCompletion(state: Any?) {}

    // ...   
}
复制代码

state的生成逻辑:

internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
        loopOnState { state ->
            val finalState = tryMakeCompleting(state, proposedUpdate)
            when {
                finalState === COMPLETING_ALREADY ->
                    throw IllegalStateException(
                        "Job $this is already complete or completing, " +
                            "but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
                    )
                finalState === COMPLETING_RETRY -> return@loopOnState
                else -> return finalState // COMPLETING_WAITING_CHILDREN or final state
            }
        }
    }
复制代码

它的作用是完成这个Job,当Job已完成时调用会抛出IllegalStateException。 state获取完,如果实在等待子项完成,则直接return,否则调用afterResume,afterResume直接调用了afterCompletion。 afterCompletion在不同的子类中实现:

  • BlockingCoroutine
    override fun afterCompletion(state: Any?) {
        // 唤醒阻塞线程
        if (Thread.currentThread() != blockedThread)
            unpark(blockedThread)
    }

复制代码
  • ScopeCoroutine
    override fun afterCompletion(state: Any?) {
        // Resume in a cancellable way by default when resuming from another context
        uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))
    }
复制代码
  • DispatchedCoroutine
    override fun afterCompletion(state: Any?) {
        // Call afterResume from afterCompletion and not vice-versa, because stack-size is more
        // important for afterResume implementation
        afterResume(state)
    }

    override fun afterResume(state: Any?) {
        if (tryResume()) return // completed before getResult invocation -- bail out
        // Resume in a cancellable way because we have to switch back to the original dispatcher
        uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))
    }
复制代码

除了BlockingCoroutine,其他两者最终都是uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))

internal actual inline fun <T> Continuation<T>.shareableInterceptedResumeCancellableWith(result: Result<T>) {
    intercepted().resumeCancellableWith(result)
}
复制代码

最终的重任又来到了resumeCancellableWith,要么DispatchedContinuation,交给调度器处理,要么直接调用Continuation自己的resumeWith。

编译器对挂起lambda的处理

在编译层面的实现在BaseContinuationImpl中:

internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {

    // ...

    public final override fun resumeWith(result: Result<Any?>) {
        // 此循环在 current.resumeWith(param) 中展开递归,以使恢复时的堆栈跟踪更清晰、更短
        var current = this
        var param = result 
        while (true) {
            // 这个方法用于debug追踪,在每个恢复的Continuation上调用“resume”调试探测器,以便调试库基础结构可以精确跟踪暂停的调用堆栈的哪一部分已经恢复
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // 尝试在未完成的情况下恢复Continuation时快速失败
                val outcome: Result<Any?> = try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // 状态机对象正在终止
                if (completion is BaseContinuationImpl) {
                    // 通过循环展开递归
                    current = completion
                    param = outcome
                } else {
                    // 顶级completion调用——invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
}
复制代码

这个方法的大致流程可以分为:

  1. 获取执行结果 大部分current,也就是当前continuation对象不为空时,尝试调用到invokeSuspend(param),如果invokeSuspend(param)返回了COROUTINE_SUSPENDED,则直接return,否则最终结果为成功Result.success(outcome),若在执行过程中捕获到异常,最终结果为失败Result.failure(exception)
  2. 释放拦截器
  3. 递归子项
  4. 使用最终结果outcome调用顶级completion的resumeWith(outcome)。

猜你喜欢

转载自juejin.im/post/7042233212302524423