如何在不中断 Kotlin 协程计算的情况下实现超时?

How to implement timeout without interrupting a calculation in Kotlin coroutine?

假设一个请求开始了长时间的计算,但等待结果的时间不会超过 X 秒。但是我不想中断计算,而是希望它并行继续直到完成。

withTimeoutOrNull 函数满足第一个条件(“等待时间不超过”)。
当结果准备好时,继续计算并执行某些最终操作的标准(惯用的 Kotlin)方法是什么?

示例

  fun longCalculation(key: Int): String { /* 2..10 seconds */}


  // ----------------------------
  cache = Cache<Int, String>()

  suspend fun getValue(key: Int) = coroutineScope {

      val value: String? = softTimeoutOrNull(
              timeout = 5.seconds(), 
              calculation = { longCalculation() },
              finalAction = { v -> cache.put(key, v) }
      )

      // return calculated value or null
  }

这是一个足够小众的案例,我认为没有就惯用的方法达成共识。

由于您希望即使当前协程正在恢复也能在后台继续工作,因此您需要一个单独的 CoroutineScope 来启动该后台工作,而不是使用 coroutineScope 构建器来启动协程作为当前协程的子协程。该外部范围将决定它启动的协程的生命周期(如果被取消,则取消它们)。通常,如果您使用的是协程,那么您手头已经有了一个与当前 class.

的生命周期关联的范围

我认为这可以满足您的描述。当前协程可以在 withTimeoutOrNull 中包装一个 Deferred.await() 调用,看看它是否可以等待另一个协程(这不是子协程,因为它是直接从外部 CoroutineScope 启动的)而不干扰它。

suspend fun getValue(key: Int): String? {
    val deferred = someScope.async {
        longCalculation(key)
           .also { cache.put(key, it) }
    }
    return withTimeoutOrNull(5000) { deferred.await() }
}

这是一个通用版本:

/**
 * Launches a coroutine in the specified [scope] to perform the given [calculation]
 * and [finalAction] with that calculation's result. Returns the result of the
 * calculation if it is available within [timeout].
 */
suspend fun <T> softTimeoutOrNull(
    scope: CoroutineScope,
    timeout: Duration,
    calculation: suspend () -> T,
    finalAction: suspend (T) -> Unit = { }
): T? {
    val deferred = scope.async {
        calculation().also { finalAction(it) }
    }
    return withTimeoutOrNull(timeout) { deferred.await() }
}