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!

待续!

相关推荐
带刺的坐椅几秒前
Solon Ai Flow 编排开发框架发布预告(效果预览)
java·ai·solon·dify·solon-flow
2302_8097983215 分钟前
【JavaWeb】JDBC
java·开发语言·servlet
小刘不想改BUG1 小时前
LeetCode LCR 010 和为 K 的子数组 (Java)
java·算法·leetcode
MeyrlNotFound1 小时前
(二十一)Java集合框架源码深度解析
java·开发语言
正在走向自律1 小时前
2025年、2024年最新版IntelliJ IDEA下载安装过程(含Java环境搭建+Maven下载及配置)
java·jvm·jdk·maven·intellij-idea
不会就选C.1 小时前
【开源分享】健康饮食管理系统(双端+论文)
java·spring boot·开源·毕业设计
永远有多远.1 小时前
【高频面试题】LRU缓存
java·缓存·面试
Ten peaches1 小时前
Selenium-Java版(环境安装)
java·前端·selenium·自动化
编程、小哥哥2 小时前
Java求职者面试:从Spring Boot到微服务的技术点解析
java·spring boot·redis·微服务·spring security·高并发·面试题
purrrew2 小时前
【Java ee初阶】jvm(1)
java·jvm·java-ee