COROUTINE_SUSPENDED 和 Kotlin 中的 suspendCoroutineOrReturn

COROUTINE_SUSPENDED and suspendCoroutineOrReturn in Kotlin

kotlin 中协程的思想是抽象挂起和回调的概念并编写简单的顺序代码。你永远不用担心协程是否被挂起,类似于线程。

suspendCoroutineOrReturnCOROUTINE_SUSPENDED 的用途是什么?您会在什么情况下使用它们?

最近在 1.1 中引入了 suspendCoroutineOrReturnCOROUTINE_SUSPENDED 内部函数,以解决特定的堆栈溢出问题。 这是一个例子:

fun problem() = async { 
  repeat(10_000) { 
    await(work()) 
  } 
}

其中 await 只是等待完成:

suspend fun <T> await(f: CompletableFuture<T>, c: Continuation<T>): Unit { 
  f.whenComplete { value, exception ->              // <- await$lambda
    if (exception != null) c.resumeWithException(exception) else 
                           c.resume(value)
  }
}

让我们看一下 work 并没有真正挂起,但 return 立即得到结果(例如缓存)的情况。 在 Kotlin 中编译协程的状态机将进行以下调用: problem$stateMachineawaitCompletableFuture.whenCompleteawait$lambdaContinuationImpl.resumeproblem$stateMachineawait、...

本质上,没有任何东西被挂起,状态机在同一个执行线程中一次又一次地调用自身,最终以 WhosebugError.

结束

一个建议的解决方案是允许await return一个特殊的标记(COROUTINE_SUSPENDED)来区分协程是否真的挂起,这样状态机就可以避免堆栈溢出。 接下来, suspendCoroutineOrReturn 用于控制协程执行。这是它的声明:

public inline suspend fun <T> suspendCoroutineOrReturn(crossinline block: (Continuation<T>) -> Any?): T

请注意,它收到一个带有延续的块。基本上它是一种访问 Continuation 实例的方法, 通常隐藏起来,只在编译期间出现。该块还允许 return 任何值或 COROUTINE_SUSPENDED.

由于这一切看起来相当复杂,Kotlin 试图将其隐藏起来并建议只使用 suspendCoroutine 函数,它会在内部为您完成上述所有工作。 这是避免 WhosebugError 的正确 await 实现(旁注:await 在 Kotlin lib 中提供,它实际上是一个扩展函数,但对于本次讨论来说并不那么重要)

suspend fun <T> await(f: CompletableFuture<T>): T = 
  suspendCoroutine { c -> 
    f.whenComplete { value, exception -> 
      if (exception != null) c.resumeWithException(exception) else
                             c.resume(value)
  }
} 

但是如果你想接管对协程延续的细粒度控制,你应该在进行外部调用时调用 suspendCoroutineOrReturn 和 return COROUTINE_SUSPENDED