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!

待续!

相关推荐
编程大师哥3 分钟前
vxe-table 透视表分组汇总及排序基础配置
java
8***848217 分钟前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
9***J62820 分钟前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
M***Z21031 分钟前
SQL 建表语句详解
java·数据库·sql
v***79431 分钟前
Spring Boot 热部署
java·spring boot·后端
执笔论英雄31 分钟前
【RL】python协程
java·网络·人工智能·python·设计模式
galaxyffang43 分钟前
认证、会话管理、授权的区别
java
未名编程1 小时前
Windows 下如何部署 Nacos 并导入配置文件
java·windows
boonya1 小时前
Java中Plugin设计模式的规范应用
java·spring·设计模式·插件模式
杰克尼1 小时前
3. 分巧克力
java·数据结构·算法