kotlin 调用suspendCoroutine块中的挂起函数的合适方法是什么?

8iwquhpp  于 2023-02-05  发布在  Kotlin
关注(0)|答案(2)|浏览(210)

在调用continuation.resume()之前,我需要在suspendCoroutine块中调用一个suspending函数,什么是合适的方法呢?

private suspend fun someFunction() = suspendCoroutine { cont ->
    //...
    val myResult = mySuspendingFunction() //<--- The IDE says "Suspension functions can be called only within coroutine body"
    cont.resume(myResult)
}
j9per5c4

j9per5c41#

不能在suspendCoroutine块中调用suspend函数,因为它接受非挂起块作为参数:

suspend inline fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T

'suspendCoroutine'主要在我们有一些带有回调的遗留代码时使用,例如:

suspend fun getUser(id: String): User = suspendCoroutine { continuation ->
      Api.getUser(id) { user ->
          continuation.resume(user)
      }
}

如果函数someFunction()没有使用回调函数调用API,那么您应该重新考虑您的方法,以去除'suspendCoroutine':

private suspend fun someFunction() {
    // ...
    val myResult = mySuspendingFunction()
    // ...
}

如果您仍然希望使用suspendCoroutine,请将mySuspendingFunction的调用移出suspendCoroutine块:

private suspend fun someFunction(): String {
    val myResult = mySuspendingFunction()

    return suspendCoroutine { cont ->
        //...
        cont.resume(myResult)
    }
}

suspend fun mySuspendingFunction(): String {
    delay(1000) // simulate request
    return "result"
}
46qrfjad

46qrfjad2#

最好避免这种情况,并且在suspendCoroutine之前调用suspending函数,正如其他人已经回答的那样,这对于所讨论的特定情况是可能的。
但是,如果您需要延续,则无法执行此操作。
(The下面是给那些因为这个原因而发现这个问题的人的,就像@Zordid和我一样。chan.send就是一个例子。)
在这种情况下,下面是一种可能的,但容易出错的方法,我 * 不 * 推荐:

suspend fun cont1() {
    //btw. for correct implementation, this should most likely be at least suspendCancellableCoroutine
    suspendCoroutine<Unit> { uCont ->
        val x = suspend { chan.send(foo(uCont)) }
        x.startCoroutine(Continuation(uCont.context) {
            if (it.isFailure)
                uCont.resumeWith(it)
            // else resumed by whatever reads from chan
        })
    }
}

(我认为错误处理本身就说明了为什么它不是一个很好的选择,尽管还有其他问题。)

一种更好、更安全、更便宜的方法是尽可能使用CompletableDeferred

如果你 * 必须 * 传递一个Continuation,那么这样做仍然更安全,而且可能更便宜:

suspend fun cont2() {
    val rslt = CompletableDeferred<Unit>()
    chan.send(foo(Continuation(currentCoroutineContext()) {
        rslt.completeWith(it)
    }))
    rslt.await()
}

相关问题