kotlin 协程源代码泛读:引子

引子:从协程的启动聊起

回顾在 Java/Android中如何启动一个线程

arduino 复制代码
val thread = Thread {
  println("hello thread!")
}
thread.start()

创建一个线程对象,然后 start,很简单!

启动一个协程(准确的讲是使用 Global coroutine scope 启动)

scss 复制代码
GlobalScope.launch {
  println("i am in the coroutine")
}

乍看并没有发现诸如创建协程(coroutine)对象,启动之类的方法,代以口语化的 launch 调用

它一定在后面帮我们做了很多事情(封装)

kotlin 复制代码
public fun CoroutineScope.launch(
  context: CoroutineContext = EmptyCoroutineContext,
  start: CorotuineStart = CoutineStart.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
}

emm,一堆新名词(概念、抽象)!

java 复制代码
CoroutineScope
CoroutineStart
CoroutineContext
Coroutine (StandaloneCoroutine, LazyStandaloneCoroutine)
Job

好在命名很规范,即使对协程一窍不通,从名字也能猜出一二,先不着急展开

总结:launch 这个协程 builder 帮我们干(封装)了很多事,避免了写一堆样板代码,包括:

  1. 创建默认的协程上下文(CoroutineContext)
  2. 创建协程对象(Coroutine)
  3. 启动协程(start)

启动协程的代码和(创建)和启动线程的代码很像

lua 复制代码
/*
start: 启动参数,或者说是启动模式
coroutine: 思考为什么要把 coroutine 自个儿传进去?
block: 这个好理解,协程要执行的代码片段
*/
coroutine.start(start, coroutine, block)

接着 STEP INTO,start 的具体实现在 AbstractCoroutine.kt 中

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

emmm,是眼花了吗?好像递归了?start 方法里头的 start 调用的是 CoroutineStart 的invoke operator(函数调用操作符重载)...

kotlin 复制代码
// CoroutineStart.kt
public operator fun<R, T> invoke(
    block: suspend R.() -> T,
    receiver: R, completion: Continuation<T>,
    completion: Continuation<T>
): Unit = {
    when(this) {
        DEFAULT -> block.startCoroutineCancellable(receiver, completion)
    ......

    }
}

又是扩展函数!,kotlin coroutnie 框架里用到了大量的扩展函数,习惯就好...

kotlin 复制代码
// Cancelable.kt
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)
    }
}

runSafely,这个也是一目了然,为了安全的执行尾部的 lamda 表达式经验丰富的你应该能猜到startCoroutineCancellable 和 createCoroutineUnintercepted 也是扩展方法

仔细观察链式调用三连:create/intercepted/resume:

  1. 创建 create
  2. 拦截 intercepte
  3. 恢复 resume

创建好理解,但是拦截和恢复是什么鬼?随着对源代码理解的深入,慢慢细品

创建:createCoroutineUnintercepted 并不是创建 coroutine,而是创建一个叫 Continuation 的东西,你可以把 Continuation 想象成对状态机的封装,通过 Continuation (resumeWith)执行 suspend lambda(block)

恢复:这是真正执行协程(suspend lambda)的入口,如果把 suspend lambda 想象成一个机器 拦截:包装器模式,它拦截了对resume的调用,使得代码可以被调度,说的直白点,可以在线程池中执行

我们来看看创建方法

kotlin 复制代码
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 is BaseContinuationImpl 是成立的,通过 create 方法创建一个 Continuation 第一次看到这段代码是有疑问的, 这个 create 可以推测出是 BaseContinuationImpl 的一个方法,查看相关源码也能证实这一点

但是,为什么 suspend lambda is 啊 BaseContinuationImpl? 为了寻求答案,可以反编译(开头那个)简单的例子

kotlin 复制代码
fun main() {
    GlobalScope.launch {
        println("hello, coroutine")
    }
}

下面这一长串有点冲稿费的嫌疑,可以重点关注注释的那几行

bash 复制代码
// access flags 0x19
  public final static main()V
   L0
    LINENUMBER 7 L0
    GETSTATIC kotlinx/coroutines/GlobalScope.INSTANCE : Lkotlinx/coroutines/GlobalScope;
    CHECKCAST kotlinx/coroutines/CoroutineScope
    ACONST_NULL
    ACONST_NULL

    // 创建 一个内部类的对象,它就是 launch 的那个 suspend lambda
    NEW com/dreamworker/kotlin/coroutine/RunBlockingKt$main$1
    DUP
    ACONST_NULL
    INVOKESPECIAL com/dreamworker/kotlin/coroutine/RunBlockingKt$main$1.<init> (Lkotlin/coroutines/Continuation;)V
    CHECKCAST kotlin/jvm/functions/Function2
    ICONST_3
    ACONST_NULL
    INVOKESTATIC kotlinx/coroutines/BuildersKt.launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
    POP
   L1
    LINENUMBER 10 L1
    NOP
    RETURN
    MAXSTACK = 6
    MAXLOCALS = 0

  // access flags 0x1009
  public static synthetic main([Ljava/lang/String;)V
   L0
    INVOKESTATIC com/dreamworker/kotlin/coroutine/RunBlockingKt.main ()V
    NOP
    RETURN
   L1
    LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1
}

RunBlockingKt <math xmlns="http://www.w3.org/1998/Math/MathML"> m a i n main </math>main1 集成自 SuspendLambda, 而 SuspendLambda is a BaseContinuationImpl

scala 复制代码
final class com/dreamworker/kotlin/coroutine/RunBlockingKt$main$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2 {...}

到此做个总结,重要的事情说三遍: suspend lambda is a Continuation suspend lambda is a Continuation suspend lambda is a Continuation

回到启动协程的地方

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

我们浏览完 create 流程,这里先忽略 intercepted,因为它涉及到协程上下文,协程拦截(代理),协程派发等概念,resumeCancellableWith 方法开始执行协程!它的实现在基类 BaseContinuationImpl 中

kotlin 复制代码
internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
): Continuation<Any?>, CoroutineStackFrame, Serializable {

    public final override resumeWith(result: Result<Any?>) {
        while (true) {
            ...
            invokeSuspend()
            ...
        }
    }
}

invokeSuspend 顾名思义,调用 suspend 方法,是个需要子类自己实现的抽象方法,

反编译 com/dreamworker/kotlin/coroutine/RunBlockingKt <math xmlns="http://www.w3.org/1998/Math/MathML"> m a i n main </math>main1 的 invokeSuspend 方法:

typescript 复制代码
public final Object invokeSuspend(Object $result) {  
   Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();  
   switch (this.label) {  
      case 0:  
         ResultKt.throwOnFailure($result);  
         String var2 = "hello, coroutine";  
         System.out.println(var2);  
         return Unit.INSTANCE;  
      default:  
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");  
   }  
}

终于看到了我们自个儿的那行 hello, coroutine!

待续!

相关推荐
Bug退退退1231 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠1 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
Zz_waiting.1 小时前
Javaweb - 10.4 ServletConfig 和 ServletContext
java·开发语言·前端·servlet·servletconfig·servletcontext·域对象
全栈凯哥1 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
兮动人1 小时前
获取终端外网IP地址
java·网络·网络协议·tcp/ip·获取终端外网ip地址
呆呆的小鳄鱼1 小时前
cin,cin.get()等异同点[面试题系列]
java·算法·面试
独立开阀者_FwtCoder1 小时前
"页面白屏了?别慌!前端工程师必备的排查技巧和面试攻略"
java·前端·javascript
Touper.1 小时前
JavaSE -- 泛型详细介绍
java·开发语言·算法
静若繁花_jingjing2 小时前
Redis线程模型
java·数据库·redis
hello早上好2 小时前
CGLIB代理核心原理
java·spring