前言

在前面文章中,咱们要点剖析了挂起函数的原理,包括常识点有:挂起函数内部其实便是CPS状态机的模型,Continuation类似于Callback,即能够用于完成挂起函数向挂起函数外传递数据,也能够运用匿名内部类办法接纳挂起函数回来值,最终便是创立挂起函数的最底层函数suspendCoroutineUninterceptedOrReturn办法其实便是为了完成状态机逻辑,一起消除suspend要害字。

上面常识点务必要明晰,如果不理解的能够查看本系列文章专栏,本篇文章就开端协程原理部分。

正文

这儿咱们又回到了Continuation.kt这个文件,由于这是协程框架的根底元素,上一篇文章咱们介绍了创立挂起函数的俩个高阶函数便是这个类中的根底层API,除此之外,在这个类,还有发动协程的根底API。

协程发动的根底API

什么是根底API呢?其实咱们前面所说的发动协程的办法,比如launchasync都是属于上层或者中间层API,它们都是调用了根底API。

已然这么重要,咱们就来看看:

//创立协程
public fun <R, T> (suspend R.() -> T).createCoroutine(
    receiver: R,
    completion: Continuation<T>
): Continuation<Unit> =
    SafeContinuation(createCoroutineUnintercepted(receiver, completion).intercepted(), COROUTINE_SUSPENDED)
//发动协程
public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

这儿能够发现createCoroutine{}startCoroutine{}都是扩展函数,而且扩展的接纳者类型是(suspend () -> T)

或许咱们经常给一些常用的类型增加扩展函数,可是几乎没有干过给函数类型增加扩展函数。已然Kotlin中,函数作为一等公民,咱们给它增加扩展函数也是能够的。

那咱们如何调用上面扩展函数呢?测验代码如下:

fun main(){
    Thread.sleep(2000L)
    testStartCoroutine()
}
/**
 * 这儿的block类型是"suspend () -> String"
 *
 * 这儿咱们秉承 单办法接口 <--> 高阶函数 <--> lambda这种关系
 * */
val block = suspend {
    println("Hello")
    delay(1000L)
    println("Kotlin")
    "Result"
}
/**
 * 这儿调用了[startCoroutine]扩展函数,这个扩展函数是 suspend () -> T 的
 * 扩展函数。
 *
 * [Continuation]有2个效果,一个是完成挂起函数时用来向外传递数据;一个是以匿名
 * 内部类的办法来接纳一个挂起函数的值。
 * */
private fun testStartCoroutine(){
    val continuation = object : Continuation<String>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
        override fun resumeWith(result: Result<String>) {
            println("Result is ${result.getOrNull()}")
        }
    }
    block.startCoroutine(continuation)
}
  • 这儿界说了变量名为blocklambda表达式,它的类型是suspend () -> String,由于lambda表达式最终一行是该lambda的回来值;一起在Kotlin中,高阶函数、单接口办法、lambda能够看成是相同的。

  • 然后界说了一个continuation变量,依据前一篇文章咱们知道Continuation有2个效果:一种是在完成挂起函数的时分,用于传递挂起函数的履行成果;另一种是在调用挂起函数的时分,以匿名内部类的办法,接纳挂起函数的履行成果。而上面代码的效果便是第二种,用来接纳block的履行成果。

这儿的这种运用办法,就感觉像是给一个挂起函数设置了Continuation参数相同,依据前面CPS原理,咱们知道每个挂起函数都需求一个Continuation参数追加到参数列表后,那这儿真是这样吗?

咱们能够经过剖析源码来解读一下。

startCoroutine{}原理解析

这儿咱们直接把上面代码进行反编译,能够得到如下Java代码:

public final class KtCreateCoroutineKt {
   @NotNull
   private static final Function1 block;
   //注释1 主函数调用
   public static final void main() {
      Thread.sleep(2000L);
      testStartCoroutine();
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
   @NotNull
   public static final Function1 getBlock() {
      return block;
   }
   //注释2 这儿创立了一个Continuation目标,可是类型无法解析
   //这是由于它是一个匿名内部类 
   private static final void testStartCoroutine() {
      <undefinedtype> continuation = new Continuation() {
         @NotNull
         public CoroutineContext getContext() {
            return (CoroutineContext)EmptyCoroutineContext.INSTANCE;
         }
         public void resumeWith(@NotNull Object result) {
            String var2 = "Result is " + (String)(Result.isFailure-impl(result) ? null : result);
            System.out.println(var2);
         }
      };
      //注释3 扩展函数变成了Java静态办法调用,参数为block和continuation
      ContinuationKt.startCoroutine(block, (Continuation)continuation);
   }
   static {
      //注释4,lambda原本是一个无参高阶函数,这儿默许会增加一个Continuation
      //同样的,这儿是匿名内部类的原因,无法具体解析出var0的类型
      Function1 var0 = (Function1)(new Function1((Continuation)null) {
         int label;
         //CPS后的状态机逻辑,当调用continuaiton的resume办法,会回调如此。
         //这儿的0分支中,调用delay后,会挂起,进入delay办法中,而且参数this也便是var0自己
         //调用完delay后,进入1分支,一起打印Kotlin,回来Result字段
         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            String var2;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               var2 = "Hello";
               System.out.println(var2);
               this.label = 1;
               if (DelayKt.delay(1000L, this) == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            var2 = "Kotlin";
            System.out.println(var2);
            return "Result";
         }
         //注释5 依据一个Continuation目标,创立一个新的Continuation目标,其实这个类型便是
         //状态机中的Continuation类型,即block完成类的类型
         @NotNull
         public final Continuation create(@NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function1 var2 = new <anonymous constructor>(completion);
            return var2;
         }
         public final Object invoke(Object var1) {
            return ((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);
         }
      });
      block = var0;
   }
}

这儿反编译的代码,如果看过文章 # 协程(15) | 挂起函数原理解析 中的CPS后的状态机原理,就不难理解,代码中要害地方,都进行了注释标注。

咱们还是来简略说明一下:

  • 注释1、2、3是testStartCoroutine()办法的调用,这儿运用匿名内部类的办法,把Continuation目标传递给startCoroutine函数。

  • 注释4便是典型的状态机逻辑,便是把本来suspend () -> String类型的block转换为var0,在这其间注释4的逻辑便是CPS后的状态机逻辑,里边有2个分支,由于在这儿面咱们调用了delay挂起函数。

  • 不同于一般的匿名内部类完成,在这儿多了注释5的办法,这说明var0所完成的接口中有create()办法,在该办法中,会依据一个Continuation参数创立var0

    这个var0其实便是block这一段lambda在经过编译器处理后的目标,其类型咱们现在只知道是Continuation的子类。

咱们接着来看一下startCoroutine{}的源码完成:

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

这儿调用了createCoroutineUnintercepted()办法:

public expect fun <T> (suspend () -> T).createCoroutineUnintercepted(
    completion: Continuation<T>
): Continuation<Unit>

会发现这儿是用expect修饰的,便是一种声明,咱们需求到协程源代码的JVM完成部分中找到对应的完成:

public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
    completion: Continuation<T>
): Continuation<Unit> {
    val probeCompletion = probeCoroutineCreated(completion)
    //注释1
    return if (this is BaseContinuationImpl)
        create(probeCompletion)
    else
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function1<Continuation<T>, Any?>).invoke(it)
        }
}

能够发现这也是(suspend () -> T)的扩展函数,所以this其实便是前面代码中的block

这儿需求注意了,前面咱们说的反编译中block的完成类类型是继承至ContinuationImpl的,这个十分重要,由于反编译代码无法完好显示出,所以注释2的第一个if就能回来ture,而这儿便是调用create(probeCompletion)函数。

而这个create()办法便是前面反编译中block完成类的create()办法:

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

在这个create办法中,会把咱们传入的continuation目标进行包裹,再次回来一个Continuation目标,依据前面文章挂起函数原理可知,这个其实就相当于第一次进入状态机,咱们新建一个Continuation目标,而这个目标类型便是var0的完成类类型。

注意了,这儿回来值是Continuation类型目标,即调用完create()办法,其实就对应着协程被创立了,和挂起函数相同,类型是Continuation类型。

所以这儿就好办了,依据前面的常识,这时调用resume,便会触发协程体的状态机入口,所以:

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

这儿的最终调用便是resume(Unit),调用完resume就会调用continuationinvokeSuspend办法,然后敞开协程的履行。

注意上面在resume()办法调用之前,还调用了intercepted()办法,咱们简略看一下:

public expect fun <T> Continuation<T>.intercepted(): Continuation<T>

这个办法在Continuation.kt类中,是根底元素,一起也是用expect修饰的,所以咱们要去Kotlin源码中找到JVM渠道的完成:

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

这儿逻辑十分简略,便是将Continuation强转为ContinuationImpl,然后调用它的intercpeted()办法,而前面咱们说过block完成类便是这个类的子类,所以强转一定能成功,而这个办法如下:

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    @Transient
    private var intercepted: Continuation<Any?>? = null
    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }
}

这儿的逻辑其实便是经过ContinuationInterceptor类来对Continuation进行拦截和处理,而这儿的处理其实便是将协程派发到线程上,这部分常识点等咱们说Dispatchers时再细说。

所以到这儿咱们就大致说理解了底层发动协程API的原理,其间block便是一个协程,它的类型必须是suspend类型的,然后本质便是一个内部类实例,父类是Function1ContinuationImpl,创立完协程便是回来一个内部类实例,即状态机。

然后调用resume(Unit)办法来触发状态机的invokeSuspend办法,然后开端其状态机逻辑。

createCoroutine{}原理剖析

startCoroutine{}对应的还有一个创立协程的根底API,办法如下:

public fun <T> (suspend () -> T).createCoroutine(
    completion: Continuation<T>
): Continuation<Unit> =
    SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)

从这儿咱们发现,它是相同调用了createCoroutineUnintercrepted办法,可是没有调用resume(Unit),即没有进入状态机。

所以上面测验代码,和下面写法是相同的:

private fun testCreateCoroutine(){
    val continuation = object : Continuation<String>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
        override fun resumeWith(result: Result<String>) {
            println("Result is ${result.getOrNull()}")
        }
    }
    //这儿手动调用resume(Unit)办法
    val c = block.createCoroutine(continuation)
    c.resume(Unit)
}

关于原理,咱们就不剖析了,和前面是相同的。

总结

本篇文章咱们才智到了创立协程的底层API,即:startCoroutine{}createCoroutine{},这个办法是suspend () -> T挂起函数的扩展函数,依据挂起函数CPS后的原理,它需求传入一个Continuation,而该办法下,挂起函数的完成类,会继承ContinuationImpl类,该类中有create()办法,然后产生一个Continuation类型的状态机目标。

最终调用resume办法来敞开状态机。

学习完本篇文章,咱们就知道,其实协程便是对挂起函数的进一步处理,下篇文章咱们就来仔细看看发动协程的launch函数的原理。