服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Kotlin笔记之协程工作原理

Kotlin笔记之协程工作原理

2021-05-21 01:26苍耳的博客 Java教程

这一章会以下面的代码为例解析一下Kotlin协程启动,挂起以及恢复的流程。

协程的状态机

Kotlin笔记之协程工作原理

这一章会以下面的代码为例解析一下协程启动,挂起以及恢复的流程:

  1. private suspend fun getId(): String { 
  2.     return GlobalScope.async(Dispatchers.IO) { 
  3.         delay(1000) 
  4.         "hearing" 
  5.     }.await() 
  6.  
  7. private suspend fun getAvatar(id: String): String { 
  8.     return GlobalScope.async(Dispatchers.IO) { 
  9.         delay(1000) 
  10.         "avatar-$id" 
  11.     }.await() 
  12.  
  13. fun main() { 
  14.     GlobalScope.launch { 
  15.         val id = getId() 
  16.         val avatar = getAvatar(id) 
  17.         println("${Thread.currentThread().name} - $id - $avatar"
  18.     } 

上面 main 方法中,GlobalScope.launch 启动的协程体在执行到 getId 后,协程体会挂起,直到 getId 返回可用结果,才会 resume launch 协程,执行到 getAvatar 也是同样的过程。协程内部实现使用状态机来处理不同的挂起点,将 GlobalScope.launch 协程体字节码反编译成 Java 代码,大致如下(有所删减):

  1. BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null
  2.     (CoroutineStart)null, (Function2)(new Function2((Continuation)null) { 
  3.     int label; 
  4.  
  5.     public final Object invokeSuspend( 
  6.  
  7.  Object $result) { 
  8.         Object var10000; 
  9.         String id; 
  10.         label17: { 
  11.             CoroutineScope $this$launch; 
  12.             switch(this.label) { 
  13.             case 0: // a 
  14.                 ResultKt.throwOnFailure($result); 
  15.                 $this$launch = this.p$; 
  16.                 this.label = 1; // label置为1 
  17.                 var10000 = getId(this); 
  18.                 if (var10000 == COROUTINE_SUSPENDED) { 
  19.                     return COROUTINE_SUSPENDED; 
  20.                 } 
  21.                 // 若此时已经有结果,则不挂起,直接break 
  22.                 break; 
  23.             case 1: // b 
  24.                 ResultKt.throwOnFailure($result); 
  25.                 var10000 = $result; 
  26.                 break; 
  27.             case 2: // d 
  28.                 id = (String)this.L$1; 
  29.                 ResultKt.throwOnFailure($result); 
  30.                 var10000 = $result; 
  31.                 break label17; // 退出label17 
  32.             default
  33.                 throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); 
  34.             } 
  35.             // c 
  36.             id = (String)var10000; 
  37.             this.L$1 = id; // 将id赋给L$1 
  38.             this.label = 2; // label置为2 
  39.             var10000 = getAvatar(id, this); 
  40.             if (var10000 == COROUTINE_SUSPENDED) { 
  41.                 return COROUTINE_SUSPENDED; 
  42.             } 
  43.         } 
  44.         // e 
  45.         String avatar = (String)var10000; 
  46.         String var5 = var9.append(var10001.getName()).append(" - ").append(id).append(" - ").append(avatar).toString(); 
  47.         System.out.println(var5); 
  48.         return Unit.INSTANCE; 
  49.     } 
  50.  
  51.      
  52.  
  53.  
  54.     public final Continuation create
  55.  
  56.  Object value,  
  57.  
  58.  Continuation completion) { 
  59.         Intrinsics.checkParameterIsNotNull(completion, "completion"); 
  60.         Function2 var3 = new <anonymous constructor>(completion); 
  61.         var3.p$ = (CoroutineScope)value; 
  62.         return var3; 
  63.     } 
  64.  
  65.     public final Object invoke(Object var1, Object var2) { 
  66.         return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE); 
  67.     } 

这里我们根据上面的注释以及字母标签来看一下执行流程(invokeSuspend 方法会在协程体中的 suspend 函数得到结果后被调用,具体是在哪里被调用的稍后会讲到):

  • a: launch 协程体刚执行到 getId 方法时,getId 方法的返回值将是 COROUTINE_SUSPENDED, 此时直接 return, 则 launch 协程体中 getId 后面的代码暂时不会执行,即 launch 协程体被挂起(非阻塞, 该线程依旧会做其它工作)。这里将 label 置为了 1. 而若此时 getId 已经有结果(内部没有调用 delay 之类的 suspend 函数等),则不挂起,而是直接 break。
  • b: 若上面 a 中 getId 返回 COROUTINE_SUSPENDED, 则当 getId 有可用结果返回后,会重新执行 launch 协程体的 invokeSuspend 方法,根据上面的 label==1, 会执行到这里检查一下 result 没问题的话就 break, 此时 id 赋值给了 var10000。
  • c: 在 a 中若直接 break 或 在 b 中得到 getId 的结果然后 break 后,都会执行到这里,得到 id 的值并把 label 置为2。然后调用 getAvatar 方法,跟 getId 类似,若其返回 COROUTINE_SUSPENDED 则 return,协程被挂起,等到下次 invokeSuspend 被执行,否则离开 label17 接着执行后续逻辑。
  • d: 若上面 c 中 getAvatar 返回 COROUTINE_SUSPENDED, 则当 getAvatar 有可用结果返回后会重新调用 launch 协程体的 invokeSuspend 方法,此时根据 label==2 来到这里并取得之前的 id 值,检验 result(即avatar),然后break label17。
  • e: c 中直接返回了可用结果 或 d 中 break label17 后,launch 协程体中的 suspend 函数都执行完毕了,这里会执行剩下的逻辑。

suspend 函数不会阻塞线程,且 suspend 函数不一定会挂起协程,如果相关调用的结果已经可用,则继续运行而不挂起,例如 async{} 返回值 Deferred 的结果已经可用时,await()挂起函数可以直接返回结果,不用再挂起协程。

这一节看了一下 launch 协程体反编译成 Java 后的代码逻辑,关于 invokeSuspend 是何时怎么被调用的,将会在下面讲到。

协程的创建与启动

 

这一节以 CoroutineScope.launch {} 默认参数为例,从源码角度看看 Kotlin 协程是怎样创建与启动的:

  1. public fun CoroutineScope.launch( 
  2.     context: CoroutineContext = EmptyCoroutineContext, 
  3.     start: CoroutineStart = CoroutineStart.DEFAULT
  4.     block: suspend CoroutineScope.() -> Unit 
  5. ): Job { 
  6.     val newContext = newCoroutineContext(context) 
  7.     val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true
  8.     coroutine.start(start, coroutine, block) 
  9.     return coroutine 
  10.  
  11. // AbstractCoroutine.kt 
  12. // receiver: StandaloneCoroutine 
  13. // block: suspend StandaloneCoroutine.() -> Unit 
  14. // private open class StandaloneCoroutine(...) : AbstractCoroutine<Unit>(...) {} 
  15. // public abstract class AbstractCoroutine<in T>(...) : JobSupport(active), Job, Continuation<T>, CoroutineScope {} 
  16. public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) { 
  17.     // 调用 CoroutineStart 中的 invoke 方法 
  18.     start(block, receiver, this) 
  19.  
  20. public enum class CoroutineStart { 
  21.     // block - StandaloneCoroutine.() -> Unit 
  22.     // receiver - StandaloneCoroutine 
  23.     // completion - StandaloneCoroutine<Unit> 
  24.     public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit = 
  25.         when (this) { 
  26.             // 根据 start 参数的类型调用不同的方法 
  27.             DEFAULT -> block.startCoroutineCancellable(receiver, completion) 
  28.             ATOMIC -> block.startCoroutine(receiver, completion) 
  29.             UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion) 
  30.             LAZY -> Unit // will start lazily 
  31.         } 

接下来看看 startCoroutineCancellable 方法:

  1. // receiver - StandaloneCoroutine 
  2. // completion - StandaloneCoroutine<Unit> 
  3. internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) = 
  4.     runSafely(completion) { 
  5.         createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit)) 
  6.     } 

createCoroutineUnintercepted 方法创建了一个 Continuation 类型(协程)的实例,即创建了一个协程:

  1. public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted( 
  2.     receiver: R, completion: Continuation<T> 
  3. ): Continuation<Unit> { 
  4.     return if (this is BaseContinuationImpl) create(receiver, completion) else // ... 

调用的是 (suspend (R) -> T) 的 createCoroutineUnintercepted 方法,(suspend (R) -> T) 就是协程体。直接看上面示例代码中 GlobalScope.launch 编译后的字节码,可以发现 CoroutineScope.launch 传入的 lambda 表达式被编译成了继承 SuspendLambda 的子类:

  1. final class Main$main$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2 

其继承关系为: SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation, 因此走 create(receiver, completion) 方法,从上面反编译出的 Java 代码可以看到 create 方法创建了一个 Continuation 实例,再看一下 Kotlin 代码编译后的字节码(包名已省略):

  1. public final create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation; 
  2. // ... 
  3. NEW Main$main$1 

从上面可以看到,create 方法创建了 Main$main$1 实例,而其继承自 SuspendLambda, 因此 create 方法创建的 Continuation 是一个 SuspendLambda 对象。

即 createCoroutineUnintercepted 方法创建了一个 SuspendLambda 实例。然后看看 intercepted 方法:

  1. public actual fun <T> Continuation<T>.intercepted(): Continuation<T> = 
  2.     // 如果是ContinuationImpl类型,则调用intercepted方法,否则返回自身 
  3.     // 这里的 this 是 Main$main$1 实例 - ContinuationImpl的子类 
  4.     (this as? ContinuationImpl)?.intercepted() ?: this 
  5.  
  6. // ContinuationImpl 
  7. public fun intercepted(): Continuation<Any?> = 
  8.     // context[ContinuationInterceptor]是 CoroutineDispatcher 实例 
  9.     // 需要线程调度 - 返回 DispatchedContinuation,其 continuation 参数值为 SuspendLambda 
  10.     // 不需要线程调度 - 返回 SuspendLambda 
  11.     intercepted ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this).also { intercepted = it } 
  12.  
  13. // CoroutineDispatcher 
  14. // continuation - SuspendLambda -> ContinuationImpl -> BaseContinuationImpl 
  15. public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = 
  16.     DispatchedContinuation(this, continuation) 

接下来看看 resumeCancellableWith 是怎么启动协程的,这里还涉及到Dispatchers线程调度的逻辑:

  1. internal class DispatchedContinuation<in T>(      
  2.  
  3.  val dispatcher: CoroutineDispatcher,      
  4.  
  5.  val continuation: Continuation<T> 
  6. ) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation { 
  7.     public fun <T> Continuation<T>.resumeCancellableWith(result: Result<T>): Unit = when (this) { 
  8.         // 进行线程调度,最后也会执行到continuation.resumeWith方法 
  9.         is DispatchedContinuation -> resumeCancellableWith(result) 
  10.         // 直接执行continuation.resumeWith方法 
  11.         else -> resumeWith(result) 
  12.     } 
  13.  
  14.     inline fun resumeCancellableWith(result: Result<T>) { 
  15.         val state = result.toState() 
  16.         // 判断是否需要线程调度 
  17.         if (dispatcher.isDispatchNeeded(context)) { 
  18.             _state = state 
  19.             resumeMode = MODE_CANCELLABLE 
  20.             // 需要调度则先进行调度 
  21.             dispatcher.dispatch(context, this) 
  22.         } else { 
  23.             executeUnconfined(state, MODE_CANCELLABLE) { 
  24.                 if (!resumeCancelled()) { 
  25.                     // 不需要调度则直接在当前线程执行协程 
  26.                     resumeUndispatchedWith(result) 
  27.                 } 
  28.             } 
  29.         } 
  30.     } 
  31.  
  32.     inline fun resumeUndispatchedWith(result: Result<T>) { 
  33.         withCoroutineContext(context, countOrElement) { 
  34.             continuation.resumeWith(result) 
  35.         } 
  36.     } 
  • 当需要线程调度时,则在调度后会调用 DispatchedContinuation.continuation.resumeWith 来启动协程,其中 continuation 是 SuspendLambda 实例;
  • 当不需要线程调度时,则直接调用 SuspendLambda.resumeWith 来启动协程。

resumeWith 方法调用的是父类 BaseContinuationImpl 中的 resumeWith 方法:

  1. internal abstract class BaseContinuationImpl(public val completion: Continuation<Any?>?) : Continuation<Any?>, CoroutineStackFrame, Serializable { 
  2.     public final override fun resumeWith(result: Result<Any?>) { 
  3.         // ... 
  4.         val outcome = invokeSuspend(param) 
  5.         // ... 
  6.     } 

因此,协程的启动是通过 BaseContinuationImpl.resumeWith 方法调用到了子类 SuspendLambda.invokeSuspend 方法,然后通过状态机来控制顺序运行。

协程的挂起和恢复

 

Kotlin 编译器会为 协程体 生成继承自 SuspendLambda 的子类,协程的真正运算逻辑都在其 invokeSuspend 方法中。上一节介绍了 launch 是怎么创建和启动协程的,在这一节我们再看看当协程代码执行到 suspend 函数后,协程是怎么被挂起的 以及 当 suspend 函数执行完成得到可用结果后是怎么恢复协程的。

Kotlin 协程的内部实现使用了 Kotlin 编译器的一些编译技术,当 suspend 函数被调用时,都有一个隐式的参数额外传入,这个参数是 Continuation 类型,封装了协程 resume 后执行的代码逻辑。

  1. private suspend fun getId(): String { 
  2.     return GlobalScope.async(Dispatchers.IO) { 
  3.         delay(1000) 
  4.         "hearing" 
  5.     }.await() 
  6.  
  7. // Decompile成Java 
  8. final Object getId( 
  9.  
  10.  Continuation $completion) { 
  11.     // ... 

其中传入的 $completion 参数,从上一节可以看到是调用 getId 方法所在的协程体对象,也就是一个 SuspendLambda 对象。Continuation的定义如下:

  1. public interface Continuation<in T> { 
  2.     public val context: CoroutineContext 
  3.  
  4.     public fun resumeWith(result: Result<T>) 

将 getId 方法编译后的字节码反编译成 Java 代码如下(为便于阅读,删减及修改了部分代码):

  1. final Object getId( 
  2.  
  3.  Continuation $completion) { 
  4.     // 新建与启动协程 
  5.     return BuildersKt.async$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)Dispatchers.getIO(), (CoroutineStart)null, (Function2)(new Function2((Continuation)null) { 
  6.         int label;  
  7.  
  8.         public final Object invokeSuspend( 
  9.  
  10.  Object $result) { 
  11.             switch(this.label) { 
  12.             case 0: 
  13.                 ResultKt.throwOnFailure($result); 
  14.                 this.label = 1; 
  15.                 if (DelayKt.delay(1000L, this) == COROUTINE_SUSPENDED) { 
  16.                     return COROUTINE_SUSPENDED; 
  17.                 } 
  18.                 break; 
  19.             case 1: 
  20.                 ResultKt.throwOnFailure($result); 
  21.                 break; 
  22.             default
  23.                 throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); 
  24.             } 
  25.             return "hearing"
  26.         } 
  27.  
  28.         // ... 
  29.     }), 2, (Object)null).await($completion); // 调用 await() suspend 函数 

结合协程的状态机一节,当上面的 launch 协程体执行到 getId 方法时, 会根据其返回值是否为 COROUTINE_SUSPENDED 来决定是否挂起,由于 getId 的逻辑是通过 async 启动一个新的协程,协程体内调用了 suspend delay 方法,然后通过 await suspend 函数等待结果,当 async 协程没完成时, await 会返回 COROUTINE_SUSPENDED, 因此 launch 协程体的 invokeSuspend 方法直接 return COROUTINE_SUSPENDED 值执行完成,此时 launch 启动的协程处于挂起状态但不阻塞所处线程,而 async 启动的协程开始执行。

我们看一下 async 的源码:

  1. public fun <T> CoroutineScope.async(...): Deferred<T> { 
  2.     val newContext = newCoroutineContext(context) 
  3.     val coroutine = if (start.isLazy) LazyDeferredCoroutine(newContext, block) else 
  4.         DeferredCoroutine<T>(newContext, active = true
  5.     coroutine.start(start, coroutine, block) 
  6.     return coroutine 

默认情况下,上面的 coroutine 取 DeferredCoroutine 实例,于是我们看一下其 await 方法以及在 async 协程执行完成后,是怎么恢复 launch 协程的:

  1. private open class DeferredCoroutine<T>( 
  2.     parentContext: CoroutineContext, active: Boolean 
  3. ) : AbstractCoroutine<T>(parentContext, active), Deferred<T>, SelectClause1<T> { 
  4.     override suspend fun await(): T = awaitInternal() as T 
  5.  
  6. // JobSupport 
  7. internal suspend fun awaitInternal(): Any? { 
  8.     while (true) { // lock-free loop on state 
  9.         val state = this.state 
  10.         if (state !is Incomplete) { 
  11.             // 已经完成,则直接返回结果 
  12.             if (state is CompletedExceptionally) { // Slow path to recover stacktrace 
  13.                 recoverAndThrow(state.cause) 
  14.             } 
  15.             return state.unboxState() 
  16.         } 
  17.         // 不需要重试时直接break,执行awaitSuspend 
  18.         if (startInternal(state) >= 0) break 
  19.     } 
  20.     return awaitSuspend() // slow-path 
  21.  
  22. // suspendCoroutineUninterceptedOrReturn: 获取当前协程,且挂起当前协程(返回COROUTINE_SUSPENDED)或不挂起直接返回结果 
  23. private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont -> 
  24.     val cont = AwaitContinuation(uCont.intercepted(), this) 
  25.     cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler)) 
  26.     cont.getResult() 

上面 awaitInternal 的大致逻辑是当挂起函数已经有结果时则直接返回,否则挂起父协程,然后 invokeOnCompletion 方法将 ResumeAwaitOnCompletion 插入一个队列(state.list)中,源码就不再贴出了。接着看看在 async 执行完成后是怎么调用 ResumeAwaitOnCompletion 来 resume 被挂起的协程的。注意:不要绕进 async 协程体中 delay 是怎么挂起和恢复 async 协程的这一逻辑,我们不需要关注这一层!

接着 async 协程的执行往下看,从前面可知它会调用 BaseContinuationImpl.resumeWith 方法来执行协程逻辑,我们详细看一下这个方法,在这里会执行该协程的 invokeSuspend 函数:

  1. internal abstract class BaseContinuationImpl( 
  2.     public val completion: Continuation<Any?>? 
  3. ) : Continuation<Any?>, CoroutineStackFrame, Serializable { 
  4.     public final override fun resumeWith(result: Result<Any?>) { 
  5.         var current = this 
  6.         var param = result 
  7.         while (true) { 
  8.             with(current) { 
  9.                 val completion = completion!! // fail fast when trying to resume continuation without completion 
  10.                 val outcome: Result<Any?> = 
  11.                     try {// 调用 invokeSuspend 方法执行协程逻辑 
  12.                         val outcome = invokeSuspend(param) 
  13.                         // 协程挂起时返回的是 COROUTINE_SUSPENDED,即协程挂起时,resumeWith 执行结束 
  14.                         // 再次调用 resumeWith 时协程挂起点之后的代码才能继续执行 
  15.                         if (outcome === COROUTINE_SUSPENDED) return 
  16.                         Result.success(outcome) 
  17.                     } catch (exception: Throwable) { 
  18.                         Result.failure(exception) 
  19.                     } 
  20.                 releaseIntercepted() // this state machine instance is terminating 
  21.                 if (completion is BaseContinuationImpl) { 
  22.                     // unrolling recursion via loop 
  23.                     current = completion 
  24.                     param = outcome 
  25.                 } else { 
  26.                     // top-level completion reached -- invoke and return 
  27.                     completion.resumeWith(outcome) 
  28.                     return 
  29.                 } 
  30.             } 
  31.         } 
  32.     } 

我们从上面的源码可以看到,在 createCoroutineUnintercepted 方法中创建的 SuspendLambda 实例是 BaseContinuationImpl 的子类对象,其 completion 参数为下:

  • launch: if (isLazy) LazyStandaloneCoroutine else StandaloneCoroutine
  • async: if (isLazy) LazyDeferredCoroutine else DeferredCoroutine

上面这几个类都是 AbstractCoroutine 的子类。而根据 completion 的类型会执行不同的逻辑:

  • BaseContinuationImpl: 执行协程逻辑
  • 其它: 调用 resumeWith 方法,处理协程的状态,协程挂起后的恢复即与它有关

在上面的例子中 async 启动的协程,它也会调用其 invokeSuspend 方法执行 async 协程逻辑,假设 async 返回的结果已经可用时,即非 COROUTINE_SUSPENDED 值,此时 completion 是 DeferredCoroutine 对象,因此会调用 DeferredCoroutine.resumeWith 方法,然后返回,父协程的恢复逻辑便是在这里。

  1. // AbstractCoroutine 
  2. public final override fun resumeWith(result: Result<T>) { 
  3.     val state = makeCompletingOnce(result.toState()) 
  4.     if (state === COMPLETING_WAITING_CHILDREN) return 
  5.     afterResume(state) 

在 makeCompletingOnce 方法中,会根据 state 去处理协程状态,并执行上面插入 state.list 队列中的 ResumeAwaitOnCompletion.invoke 来恢复父协程,必要的话还会把 async 的结果给它,具体代码实现太多就不贴了,不是本节的重点。直接看 ResumeAwaitOnCompletion.invoke 方法:

  1. private class ResumeAwaitOnCompletion<T>( 
  2.     job: JobSupport, private val continuation: CancellableContinuationImpl<T> 
  3. ) : JobNode<JobSupport>(job) { 
  4.     override fun invoke(cause: Throwable?) { 
  5.         val state = job.state 
  6.         assert { state !is Incomplete } 
  7.         if (state is CompletedExceptionally) { 
  8.             // Resume with with the corresponding exception to preserve it 
  9.             continuation.resumeWithException(state.cause) 
  10.         } else { 
  11.             // resume 被挂起的协程 
  12.             continuation.resume(state.unboxState() as T) 
  13.         } 
  14.     } 

这里的 continuation 就是 launch 协程体,也就是 SuspendLambda 对象,于是 invoke 方法会再一次调用到 BaseContinuationImpl.resumeWith 方法,接着调用 SuspendLambda.invokeSuspend, 然后根据 label 取值继续执行接下来的逻辑!

suspendCoroutineUninterceptedOrReturn

接下来我们看一下怎么将一个基于回调的方法改造成一个基于协程的 suspend 方法,要实现这个需求,重点在于 suspendCoroutineUninterceptedOrReturn 方法,根据注释,这个方法的作用是: Obtains the current continuation instance inside suspend functions and either suspends currently running coroutine or returns result immediately without suspension. 即获取当前协程的实例,并且挂起当前协程或不挂起直接返回结果。函数定义如下:

  1. public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T { 
  2.     // ... 

根据 block 的返回值,有两种情况:

  • 如果 block 返回 COROUTINE_SUSPENDED, 意味着 suspend 函数会挂起当前协程而不会立即返回结果。这种情况下, block 中的 Continuation 需要在结果可用后调用 Continuation.resumeWith 来 resume 协程。
  • 如果 block 返回的 T 是 suspend 函数的结果,则协程不会被挂起, block 中的 Continuation 不会被调用。

调用 Continuation.resumeWith 会直接在调用者的线程 resume 协程,而不会经过 CoroutineContext 中可能存在的 ContinuationInterceptor。建议使用更安全的 suspendCoroutine 方法,在其 block 中可以同步或在异步线程调用 Continuation.resume 和 Continuation.resumeWithException:

  1. public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T { 
  2.     contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } 
  3.     return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> -> 
  4.         // 调用拦截器 
  5.         val safe = SafeContinuation(c.intercepted()) 
  6.         block(safe) 
  7.         safe.getOrThrow() 
  8.     } 

此外除了 suspendCoroutine 方法,还有 suspendCancellableCoroutine, suspendAtomicCancellableCoroutine, suspendAtomicCancellableCoroutineReusable 等方法都可以用来将异步回调的方法封装成 suspend 函数。

下面来看一个例子来介绍怎么将异步回调函数封装成 suspend 函数:

  1. class NetFetcher { 
  2.     // 将下面的 request 方法封装成 suspend 方法 
  3.     suspend fun requestSuspend(id: Int): String = suspendCoroutine { continuation -> 
  4.         request(id, object : OnResponseListener { 
  5.             override fun onResponse(response: String) { 
  6.                 continuation.resume(response) 
  7.             } 
  8.  
  9.             override fun onError(error: String) { 
  10.                 continuation.resumeWithException(Exception(error)) 
  11.             } 
  12.         }) 
  13.     } 
  14.  
  15.     fun request(id: Int, listener: OnResponseListener) { 
  16.         Thread.sleep(5000) 
  17.         if (id % 2 == 0) { 
  18.             listener.onResponse("success"
  19.         } else { 
  20.             listener.onError("error"
  21.         } 
  22.     } 
  23.  
  24.     interface OnResponseListener { 
  25.         fun onResponse(response: String) 
  26.         fun onError(error: String) 
  27.     } 
  28.  
  29. object Main { 
  30.     fun main() { 
  31.         requestByCoroutine() 
  32.     } 
  33.  
  34.     // 使用回调 
  35.     private fun requestByCallback() { 
  36.         NetFetcher().request(21, object : NetFetcher.OnResponseListener { 
  37.             override fun onResponse(response: String) { 
  38.                 println("result = $response"
  39.             } 
  40.  
  41.             override fun onError(error: String) { 
  42.                 println("result = $error"
  43.             } 
  44.         }) 
  45.     } 
  46.  
  47.     // 使用协程 
  48.     private fun requestByCoroutine() { 
  49.         GlobalScope.launch(Dispatchers.Main) { 
  50.             val result = withContext(Dispatchers.IO) { 
  51.                 try { 
  52.                     NetFetcher().requestSuspend(22) 
  53.                 } catch (e: Exception) { 
  54.                     e.message 
  55.                 } 
  56.             } 
  57.       

为加深理解,再介绍一下 Kotlin 提供的两个借助 suspendCancellableCoroutine 实现的挂起函数: delay & yield。

delay

delay 方法借助了 suspendCancellableCoroutine 方法来挂起协程:

  1. public suspend fun delay(timeMillis: Long) { 
  2.     if (timeMillis <= 0) return // don't delay 
  3.     return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> -> 
  4.         cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont) 
  5.     } 
  6.  
  7. override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) { 
  8.     postDelayed(Runnable { 
  9.         with(continuation) { resumeUndispatched(Unit) } 
  10.     }, timeMillis) 

可以看出这里 delay 的逻辑类似于 Handle 机制,将 resumeUndispatched 封装的 Runnable 放到一个队列中,在延迟的时间到达便会执行 resume 恢复协程。

yield

yield 方法作用是挂起当前协程,这样可以让该协程所在线程运行其他逻辑,当其他协程执行完成或也调用 yield 让出执行权时,之前的协程可以恢复执行。

  1. launch(Dispatchers.Main) { 
  2.     repeat(3) { 
  3.         println("job1 $it"
  4.         yield() 
  5.     } 
  6. launch(Dispatchers.Main) { 
  7.     repeat(3) { 
  8.         println("job2 $it"
  9.         yield() 
  10.     } 
  11.  
  12. // output 
  13. job1 0 
  14. job2 0 
  15. job1 1 
  16. job2 1 
  17. job1 2 
  18. job2 2 

看一下 yield 的源码:

  1. public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont -> 
  2.     val context = uCont.context 
  3.     // 如果协程没有调度器,或者像 Unconfined 一样没有进行调度则直接返回 
  4.     val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit 
  5.     if (cont.dispatcher.isDispatchNeeded(context)) { 
  6.         // this is a regular dispatcher -- do simple dispatchYield 
  7.         cont.dispatchYield(context, Unit) 
  8.     } else { 
  9.         // This is either an "immediate" dispatcher or the Unconfined dispatcher 
  10.         // ... 
  11.     } 
  12.     COROUTINE_SUSPENDED 
  13.  
  14. // DispatchedContinuation 
  15. internal fun dispatchYield(context: CoroutineContext, value: T) { 
  16.     _state = value 
  17.     resumeMode = MODE_CANCELLABLE 
  18.     dispatcher.dispatchYield(context, this) 
  19.  
  20. public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block) 

可知 dispatchYield 会调用到 dispatcher.dispatch 方法将协程分发到调度器队列中,这样线程可以执行其他协程,等到调度器再次执行到该协程时,会 resume 该协程。

总结

 

通过上面协程的工作原理解析,可以从源码中发现 Kotlin 中的协程存在着三层包装:

  • 第一层包装: launch & async 返回的 Job, Deferred 继承自 AbstractCoroutine, 里面封装了协程的状态,提供了 cancel 等接口;
  • 第二层包装: 编译器生成的 SuspendLambda 子类,封装了协程的真正执行逻辑,其继承关系为 SuspendLambda -> ContinuationImpl -> BaseContinuationImpl, 它的 completion 参数就是第一层包装实例;
  • 第三层包装: DispatchedContinuation, 封装了线程调度逻辑,它的 continuation 参数就是第二层包装实例。

这三层包装都实现了 Continuation 接口,通过代理模式将协程的各层包装组合在一起,每层负责不同的功能,如下图:

Kotlin笔记之协程工作原理

原文地址:https://ljd1996.github.io/2021/05/19/Kotlin%E7%AC%94%E8%AE%B0%E4%B9%8B%E5%8D%8F%E7%A8%8B%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/?utm_source=tuicool&utm_medium=referral

延伸 · 阅读

精彩推荐