第一步
第二步
start() 的第二、三个参数是同一个,都是第一张图中构建的 StandaloneCoroutine 对象
第三步
第二步中 CoroutineStart 实现了 invoke() 方法,所以可以直接当方法调用。
第四步
第五步
返回 lambda 表达式对应的对象。
可以看到方法名前有 actual 修饰,表示它是在一个具体的平台中的实现。
create() 方法定义在 BaseContinuationImpl 中,但该方法未实现,具体的实现在 lambda 对应的类中。比如在 MainActivity 中如下书写,对应的类名就是 MainActivity$test$1:
private fun test(){
mScope.launch {
Log.e(TAG, "test:" )
}
}
通过 Javap 反编译该 class 文件,查看对应的 create() 方法,字节码如下,从指令可以看出该方法就是返回 launch() 中 lambda 表达式对应的类的对象。
从字节码还可以看出虽然传入了两个参数,但只使用了第一个。而且通过构造函数的可以发现,它只是将参数传入给父类。构造函数对应的指令如下:
第五步半
从第五步可以看出第四步中的第一个方法 createCoroutineUnintercepted() 返回的就是 lambda 对应的类对象,该类的父类是 SuspendLambda,但它并没有额外逻辑。
现在到 SuspendLambda 的父类 ContinuationImpl 的构造函数
ContinuationImpl 会调用父类 BaseContinuationImpl 构造函数,父类会使用 completion 记录下传入的参数,也就是第一步创建的 StandaloneCoroutine 对象。
到此第四步中第一个方法算是分析完成,总结一下就是返回 lambda 对应的类对象,同时使用 completion 引用第一步创建的 StandaloneCoroutine 对象
第六步
书接第四步,第五步及第五步半分析完了第一个方法,下面就该调用 intercepted() 方法。该方法具体实现如上图。第一次调用时 intercepted() 肯定为 null,所以执行到
// 注意这个 this,它就是 lambda 表达式对应的类对象
context[ContinuationInterceptor]?.interceptContinuation(this)
前面返回的是 Dispatchers 对象,interceptContinuation() 实现在 CoroutineDispatcher 中(所有 Dispatchers 的父类)
构造函数如下:
所以第六步其实就是返回一个 DispatchedContinuation 对象。
现在总结一下:DispatchedContinuation 的 continuation 属性指向 lambda 对象,lambda 对象使用 completion 指向 launch() 中创建的 StandaloneCoroutin 对象
第七步
书接第四步。从第五步到第六步都执行完后,就到了第四步中的 resumeCancellableWith()。if 判断只有在 Dispatchers.Unconfined 时才不成立,忽略。
第八步
线程切换核心点
第七步的 dispatcher#dispatch() 最终会到 CoroutineScheduler 的 dispatch(),整个过程比较简单,忽略。下面都以 Dispatchers.Default 为例进行说明。
从上图可以看出该步主要涉及到线程切换,此处不细分析。但无论线程如何切换,最终肯定执行 DispatchedContinuation 的 run() 方法
第九步
DispatchedContinuation 本身并没有重写 run() 方法,具体的实现都在其父类 DispatchedTask 中
这一步结束后 DispatchedContinuation 的功能就完成了:它主要用来进行线程切换,lambda 对象的 resume() 就是运行在切换后的线程中。
第十步
第九步到 Continuation 的 resume(),它会调用 resumeWith()
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
而 resumeWith() 定义在 BaseContinuationImpl 中,它是 lambda 对象的祖先类。上面说过 lambda 对象通过 completion 属性引用第一步创建的 StandaloneCoroutine 对象,completion 就是定义在 BaseContinuationImpl 类中
*invokeSuspend()* 逻辑比较简单,它内部就是状态机。
从逻辑上讲 completion 是当前协程的父协程(不一定是直接父协程),所以当前协程执行完成后需要再次循环调用父协程的 invokeSuspend(),这样父协程才能在挂起点恢复执行,并且能拿到子协程的返回值。
completion#resumeWith() 也是进行恢复操作,它有可能会恢复外层协程。总之,通过 resumeWith() 协程会从内到外依次被恢复
第十一步
第十步中最后执行到 completion.resumeWith()。该方法是 final 类型的,所以无论哪种 coroutine 逻辑都一样。该方法定义在 AbstractCoroutine 类中,它是 StandaloneCoroutine 的父类
makeCompletingOnce() 就是返回参数,忽略。
如果子类未重写任何方法,afterResume() 会调用 afterCompletion()。后者方法定义在 JobSupport 中,是个空实现。至此整个协程执行结束。这也是通过 launch() 启动一个协程的所有流程。
如果协程中调用了挂起函数,那么 completion.resumeWith() 就需要负责恢复它的外层协程。此处以 withContext() 为例说明
第十一步半
如果使用 withContext,lambda 表达式对应的对象依旧是 BaseContinuationImpl 类型,依旧使用 completion 引用外层。使用 launch() 时外层是 StandaloneCoroutine 类型,使用 withContext() 时则是 DispatchedCoroutine类型(具体过程不分析),该类重写了 afterResume(),该方法的入参就是 withContext() 的返回值。
最后一行,它的执行逻辑与第四步开始时一样,有几点不同:
-
第七步
resumeCancellableWith的第一个参数是withContext()的返回结果的包装。同时,第七步会使用_state记录下结果(这是withContext()的真正返回结果) -
第九步,使用
takeState()取上第七步保存的_state,并把该结果通过resume()方法会给了withContext()的外层协程 -
第十步,
invokeSuspend()就收到了内层协程的返回值
线程切换简说
从上面十几步可以发现:
-
lambda 表达式会被转换成一个对象(称为
Continuation对象),该对象在内部会被包装成DispatchedContinuation对象(即第四步中intercepted()的作用)。后者会使用Dispatchers进行线程切换。一旦线程切换完成DispatchedContinuation对象的作用就结束了,它会在新线程中执行Continuation对象的invokeSuspend()方法,也就是我们书写的 lambda 表达式 -
在 lambda 表达式中遇到新协程时,会按上面的流程构造了下新的对象进行线程切换并执行。同时内层
Continuation对象会持有外层Continuation对象的引用,方便恢复外层协程的执行 -
如果是挂起函数,在执行完成时会将外层
Continuation对象重新封装成DispatchedContinuation对象,然后再执行。这主要是为了让外层协程可以运行在挂起前的线程中。
总结
整个 launch 过程分析完成,主要遗留有线程切换逻辑,后面再说。
- 子协程间接持有父协程引用。所以子协程执行完成后,可以通过 resumeWith() 方法唤醒父协程
- 这里的唤醒其实不太准确。更准确说应该是子线程通过 resumeWith() 重新调用父协程的 invokeSuspend(),该方法内部会使用状态机模式记录父协程运行到的位置,被重新调用后会接着往下执行。
- 由于 resumeWith() 可以接收参数,所以子协程执行完成后的结果可以传递到父协程的 invokeSuspend() 中
- 所谓协程,在代码层面上就是一个 Continuation 对象。