协程挂起原理

先看一个例子:
挂起例子
日志如下:
协程挂起日志
日志里先执行了 withContext 里的代码,然后才执行 launch 后面的代码。这里 launch 后续代码也运行在单独线程中,使用的 Dispatcher 是 Dispatchers.Default。前面分析过 launch 的调度流程,launch 的 block 会先通过 createCoroutineUnintercepted 创建成一个 Continuation。这个 Continuation 的实际类型,就是编译器生成的 SuspendLambda 子类。后面即使被 DispatchedContinuation 包了一层,最终恢复时也会回到这个 SuspendLambda 的 resumeWith,再进入它的 invokeSuspend 方法: launch编译完后的suspendLamda
执行到 withContext 时,这个例子会返回 COROUTINE_SUSPENDED,所以 invokeSuspend 直接返回 var2,此时 label 已经被设置成 1。等 withContext 执行完,会通知传给 withContext 的 continuation,也就是上面的 CoroutineDispatchersActivity$demo2$1 这个 SuspendLambda,于是再次执行它的 invokeSuspend 方法。第二次进来时 label=1,最后返回 Unit.INSTANCE,整个 invokeSuspend 执行结束,这就是这里发生挂起和恢复的原因。 那为什么 CoroutineDispatchersActivity$demo2$1 遇到 COROUTINE_SUSPENDED 后不会继续往下执行?看下 BaseContinuationImpl 中的 resumeWith 逻辑: BaseContinuationImpl的resumeWith方法
也就是说,BaseContinuationImpl.resumeWith 调完 invokeSuspend 后,只要拿到 COROUTINE_SUSPENDED,就会直接 return,外层 continuation 暂时不会被继续调用。

再看 CoroutineDispatchersActivity$demo2$1 的 invokeSuspend 是什么时候被第二次执行的,可以追溯到 withContext: withContext启动协程
这里执行了 block 的 startCoroutineCancellable: 挂起函数的startCoroutineCancellable
这里和启动协程的流程很像,本质上是把 withContext 的 block 包成新的协程来执行。先看 completion 的回调,挂起函数执行完后,会执行 completion 的 resumeWith 方法。这里的 completion 是 DispatchedCoroutine 对象,它继承自 ScopeCoroutine: DispatchedCoroutine 的 afterResume 方法 afterResume 里会先取 continuation 的 context 中的 ContinuationInterceptor,也就是上面 launch 启动时使用的 dispatcher,对应的是 Dispatchers.Default。然后拿到 intercepted 后的 continuation,调用它的 resumeCancellableWith,最终把外层协程恢复到指定线程中继续执行。所以日志里能看到,withContext 执行完后,launch 后面的代码又回到了自己的线程。

withContext 执行完后触发外层协程的 resumeWith,launch 对应的 invokeSuspend 就会再次执行,而且会回到自己的 dispatcher 线程上。

总结:
协程代码块会被编译成 SuspendLambda,它本身也是一个 Continuation。执行 SuspendLambda 的 resume 时,会进入 invokeSuspend 方法。invokeSuspend 里如果调用了子协程或者挂起函数,并且拿到 COROUTINE_SUSPENDED,父协程的 resumeWith 就会直接 return,后面的代码暂时不执行。等子协程执行完后,它会恢复父协程,父协程的 invokeSuspend 再次执行。此时 label 状态已经变化,状态机会从上次挂起的位置继续往下走。父协程执行结束后,再继续回调自己的 completion。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计