Kotlin协程源码分析-4 状态机

青旅半醒 2023-07-17 15:33 136阅读 0赞

今天就来分析所谓的协程状态机

首先来看一个问题,我们知道suspend 函数会在编译后生成一个传入continuation的函数。
那么我们如何自己拿到这个传入的对象呢?这个对象又有什么作用?
请看如下代码即可

  1. //com.example.studycoroutine.chapter.four.MyCoroutine.kt
  2. suspend fun commonSuspendFun(): String {
  3. return "[hello world] "
  4. }
  5. suspend fun commonSuspendFun2(): String {
  6. return "[hello world 2] "
  7. }
  8. suspend fun commonSuspendFun3(): String {
  9. return "[hello world 3]"
  10. }
  11. fun testFunGetContinuation() {
  12. val mystartSuspend: suspend () -> String = {
  13. //返回一个字符串hello world
  14. val one = commonSuspendFun()
  15. //返回一个字符串hello world2
  16. val two = commonSuspendFun2()
  17. //返回一个字符串hello world3
  18. val three = commonSuspendFun3()
  19. one+two+three
  20. }
  21. val myCoroutine = MyCoroutine()
  22. //mystartSuspend.createCoroutine本质也是下面的调用不过封装了代理,不方便我们学习
  23. val createCoroutine = mystartSuspend.createCoroutineUnintercepted(myCoroutine)
  24. createCoroutine.resume(Unit)
  25. }
  26. //com.example.studycoroutine.chapter.two.MyCoroutine.kt
  27. class MyCoroutine() : Continuation<String> {
  28. override fun resumeWith(result: Result<String>) {
  29. logD("MyCoroutine 回调resumeWith 返回的结果 " + result.getOrNull())
  30. }
  31. override val context: CoroutineContext
  32. get() = kotlin.coroutines.EmptyCoroutineContext
  33. }

输出结果:

[main] MyCoroutine 回调resumeWith 返回的结果 [hello world] [hello world 2] [hello world 3]

想要弄懂就必须从编译后的字节码入手.

反编译结果结构图:
在这里插入图片描述

MyCoroutineKt.testFunGetContinuation.mystartSuspend.1对应我们的suspend lambda对象mystartSuspend。而这个对象主要作用就是用来维护 状态机,这个也懂kotlin 协程的关键。

MyCoroutineKt.testFunGetContinuation.mystartSuspend.1被我改名为MystartSuspend,方便查看编译后的源码,下面的反编译字节码翻译成java语法,部分被我修饰了。

  1. class MystartSuspend extends SuspendLambda implements Function1 {
  2. Object saveResultOne;
  3. Object saveResultTwo;
  4. int label;
  5. MystartSuspend(Continuation continuation) {
  6. super(1, continuation);
  7. }
  8. public final Continuation create(Continuation continuation) {
  9. return new MystartSuspend(continuation);
  10. }
  11. public final Object invoke(Object continuation) {
  12. return ((MystartSuspend) this.create(((Continuation) continuation))).invokeSuspend(Unit.INSTANCE);
  13. }
  14. public final Object invokeSuspend(Object $result) {
  15. Object invokeResult;
  16. Object v3;
  17. String v1_2;
  18. Object resultThree;
  19. String resultOne;
  20. String resultTwo;
  21. Object SUSPEND_FLAG = IntrinsicsKt.getCOROUTINE_SUSPENDED();
  22. int v1 = this.label;
  23. if (v1 == 0) {
  24. ResultKt.throwOnFailure($result);
  25. this.label = 1;
  26. invokeResult = MyCoroutineKt.coMystartSuspendonSuspendFun(((Continuation) this));
  27. if (invokeResult == SUSPEND_FLAG) {
  28. return SUSPEND_FLAG;
  29. }
  30. label_40:
  31. v1_2 = (String) invokeResult;
  32. this.saveResultOne = v1_2;
  33. this.label = 2;
  34. v3 = MyCoroutineKt.coMystartSuspendonSuspendFun2(((Continuation) this));
  35. if (v3 == SUSPEND_FLAG) {
  36. return SUSPEND_FLAG;
  37. }
  38. label_46:
  39. String two = (String) v3;
  40. this.saveResultOne = v1_2;
  41. this.saveResultTwo = two;
  42. this.label = 3;
  43. resultThree = MyCoroutineKt.coMystartSuspendonSuspendFun3(((Continuation) this));
  44. if (resultThree == SUSPEND_FLAG) {
  45. return SUSPEND_FLAG;
  46. }
  47. resultOne = v1_2;
  48. resultTwo = two;
  49. } else {
  50. if (v1 != 1) {
  51. if (v1 != 2) {
  52. if (v1 == 3) {
  53. resultTwo = (String) this.saveResultTwo;
  54. resultOne = (String) this.saveResultOne;
  55. ResultKt.throwOnFailure($result);
  56. resultThree = $result;
  57. return resultOne + resultTwo + (((String) resultThree));
  58. }
  59. throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
  60. }
  61. v1_2 = (String) this.saveResultOne;
  62. ResultKt.throwOnFailure($result);
  63. v3 = $result;
  64. goto label_46;
  65. }
  66. ResultKt.throwOnFailure($result);
  67. invokeResult = $result;
  68. goto label_40;
  69. }
  70. return resultOne + resultTwo + (((String) resultThree));
  71. }
  72. }

调用类反编译

  1. public final class MyCoroutineKt {
  2. public static final Object commonSuspendFun(Continuation $completion) {
  3. return "[hello world] ";
  4. }
  5. public static final Object commonSuspendFun2(Continuation $completion) {
  6. return "[hello world 2] ";
  7. }
  8. public static final Object commonSuspendFun3(Continuation $completion) {
  9. return "[hello world 3]";
  10. }
  11. public static final void testFunGetContinuation() {
  12. ContinuationKt.createCoroutine(((Function1)new MystartSuspend(null)), ((Continuation)new MyCoroutine())).resumeWith(Result.constructor-impl(Unit.INSTANCE));
  13. }
  14. }

首先我们要知道创建我们的代码调用createCoroutineUnintercepted调用返回的对象是什么。

  1. //kotlin.coroutines.intrinsics.IntrinsicsJvm.kt
  2. public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
  3. completion: Continuation<T>
  4. ): Continuation<Unit> {
  5. //返回myCoroutine对象
  6. val probeCompletion = probeCoroutineCreated(completion)
  7. //if会返回true 上一篇文章有讲过lambda会编译成BaseContinuationImpl类
  8. //this 就是我们的mystartSuspend对象,也就是MystartSuspend类
  9. return if (this is BaseContinuationImpl)
  10. //调用MystartSuspend的create方法,最终返回MystartSuspend实例
  11. create(probeCompletion)
  12. else
  13. createCoroutineFromSuspendFunction(probeCompletion) {
  14. (this as Function1<Continuation<T>, Any?>).invoke(it)
  15. }
  16. }

综上所述createCoroutineUnintercepted返回MystartSuspend对象实例。
在返回MystartSuspend后我们调用resume启动协程。
MystartSuspend是一个Continuation,调用resume的函数声明,发现是一个扩展函数。

  1. public inline fun <T> Continuation<T>.resume(value: T): Unit =
  2. resumeWith(Result.success(value))

经过继承链会调用到BaseContinuationImpl对象上的resumeWith方法

  1. //kotlin.coroutines.jvm.internal.BaseContinuationImpl.kt
  2. internal abstract class BaseContinuationImpl(
  3. public val completion: Continuation<Any?>?//我们传入myCoroutine对象
  4. ) : Continuation<Any?>, CoroutineStackFrame, Serializable {
  5. public final override fun resumeWith(result: Result<Any?>) {
  6. var current = this
  7. var param = result
  8. while (true) {
  9. //先无视探针相关内容
  10. probeCoroutineResumed(current)
  11. with(current) {
  12. //用于快速错误监测有没有传入completion对象
  13. val completion = completion!!
  14. val outcome: Result<Any?> =
  15. try {
  16. //调用子类的状态机函数,这部分往往有编译器实现
  17. //当然你也可以自己实现。在我们的MystartSuspend类中
  18. //invokeSuspend函数你就看到了
  19. val outcome = invokeSuspend(param)
  20. //如果状态机返回的挂起标识那么退出循环
  21. //等候下一次调用Continuation的resumeWith函数调用在进入
  22. if (outcome === COROUTINE_SUSPENDED) return
  23. Result.success(outcome)
  24. } catch (exception: Throwable) {
  25. Result.failure(exception)
  26. }
  27. releaseIntercepted()
  28. if (completion is BaseContinuationImpl) {
  29. current = completion
  30. param = outcome
  31. } else {
  32. //如果状态返回了结果二部是挂起标识那么结束全部流程,
  33. //并且回调我么demo中MyCoroutine对象
  34. completion.resumeWith(outcome)
  35. return
  36. }
  37. }
  38. }
  39. }
  40. }

综上所述我么总结一下流程:

  1. 步骤一:我们创建一个Continuation对象用户接受挂起函数完成的通知。本案例为MyCoroutine
  2. 步骤二:创建一个suspend lambda 。本案例为mystartSuspend
  3. 步骤三:mystartSuspend 内部调用其他挂起函数
  4. 步骤四:createCoroutineUnintercepted 创建一个新的Continuation 这个Continuation 对象是步骤二的suspend lambda所生成的(编译器会生成一个SuspendLambda对象)
  5. 步骤五:调用suspend lambdacontinuationonresume.
  6. 步骤六:suspend lambdacontinuation的父类BaseContinuationImpl内部resumeWith被回调
  7. 步骤七:父类BaseContinuationImpl内部resumeWith 内部调用子类invokeSuspend并获取结果
  8. 步骤八:子类invokeSuspend调用内部的挂起函数,并把自己作为continuation传入
  9. 步骤九:获取invokeSuspend返回结果。如果返回不是COROUTINE_SUSPENDED,协程结束回调监听完成的continuation类(本案例中MyCoroutine被回调)。如果返回COROUTINE_SUSPENDED那么结束父类BaseContinuationImpl内部resumeWith的死循环,并下一次等候回调resumeWith(因为子类的invokeSuspend中调用挂起函数传入的自己(continuation)给挂起函数,挂起函数完成时回调他的resumeWith方法)
  10. 步骤十:重复步骤八和九。直到返回结果

我们这里回过头来看看编译器帮我生成类(请看注释)

  1. class MystartSuspend extends SuspendLambda implements Function1 {
  2. Object saveResultOne;
  3. Object saveResultTwo;
  4. int label;
  5. public final Object invokeSuspend(Object $result) {
  6. Object invokeResult;
  7. Object v3;
  8. String v1_2;
  9. Object resultThree;
  10. String resultOne;
  11. String resultTwo;
  12. Object SUSPEND_FLAG = IntrinsicsKt.getCOROUTINE_SUSPENDED();
  13. // (一)状态lable为0
  14. int v1 = this.label;
  15. if (v1 == 0) {
  16. ResultKt.throwOnFailure($result);
  17. // (二)状态lable为1
  18. this.label = 1;
  19. // (三) 调用第一个挂起函数,传入自己做为参数,并检查返回值
  20. //如果返回SUSPEND_FLAG就返回函数。当挂起函数用传入的Continuation
  21. //调用resume时候又重新回到这个invokeSuspend方法
  22. //当然我们这里由于不会返回SUSPEND_FLAG 所以继续向下运行
  23. invokeResult = MyCoroutineKt.coMystartSuspendonSuspendFun(((Continuation) this));
  24. if (invokeResult == SUSPEND_FLAG) {
  25. return SUSPEND_FLAG;
  26. }
  27. label_40:
  28. v1_2 = (String) invokeResult;
  29. this.saveResultOne = v1_2;
  30. //(四) 更新label状态为2
  31. this.label = 2;
  32. //(五) 调用挂起函数 与(三)相同
  33. v3 = MyCoroutineKt.coMystartSuspendonSuspendFun2(((Continuation) this));
  34. if (v3 == SUSPEND_FLAG) {
  35. return SUSPEND_FLAG;
  36. }
  37. label_46:
  38. String two = (String) v3;
  39. this.saveResultOne = v1_2;
  40. this.saveResultTwo = two;
  41. //(六) 更新lable为3
  42. this.label = 3;
  43. // (七) 调用最后一个挂起函数 同样与(三)一样检查结果
  44. resultThree = MyCoroutineKt.coMystartSuspendonSuspendFun3(((Continuation) this));
  45. if (resultThree == SUSPEND_FLAG) {
  46. return SUSPEND_FLAG;
  47. }
  48. resultOne = v1_2;
  49. resultTwo = two;
  50. } else {
  51. if (v1 != 1) {
  52. if (v1 != 2) {
  53. if (v1 == 3) {
  54. resultTwo = (String) this.saveResultTwo;
  55. resultOne = (String) this.saveResultOne;
  56. ResultKt.throwOnFailure($result);
  57. resultThree = $result;
  58. return resultOne + resultTwo + (((String) resultThree));
  59. }
  60. throw new IllegalStateException("call to \'resume\' before \'invoke\' with coroutine");
  61. }
  62. v1_2 = (String) this.saveResultOne;
  63. ResultKt.throwOnFailure($result);
  64. v3 = $result;
  65. goto label_46;
  66. }
  67. ResultKt.throwOnFailure($result);
  68. invokeResult = $result;
  69. goto label_40;
  70. }
  71. //(八)返回结果
  72. return resultOne + resultTwo + (((String) resultThree));
  73. }
  74. }

如果你逻辑还是很乱证明我没讲好。看一下图吧

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


华丽的分割线


上面的例子总是在说什么返回挂起标识COROUTINE_SUSPENDED,但是我们怎么返回这个变量?并且状态机传入Continuation对象我们怎么在挂起函数拿到?

挂起函数如何拿到Continuation

发表评论

表情:
评论列表 (有 0 条评论,136人围观)

还没有评论,来说两句吧...

相关阅读