我正在尝试了解 suspendCoroutine
和 suspendCancellableCoroutine
。我认为它们在以下情况下可能有用:
- 启动协程时,检查用户是否已登录。
- 如果没有,请提供凭据并暂停当前正在执行的协程。
- 提交凭据后,从暂停的同一行恢复协程。
这可以编译,但永远不会超过“延迟结束”,即继续永远不会恢复:
import kotlinx.coroutines.*
fun main(args: Array<String>) {
println("Hello, world!")
runBlocking {
launch {
postComment()
}
}
}
var isLoggedIn = false
var loginContinuation: CancellableContinuation<Unit>? = null
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
suspendCancellableCoroutine<Unit> {
loginContinuation = it
}
}
// call the api or whatever
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm() {
println("show login form")
// simulate delay while user enters credentials
delay(1000)
println("delay over")
isLoggedIn = true
// resume coroutine on submit
loginContinuation?.resume(Unit) { println("login cancelled") }
}
我已经尝试了我能想到的一切,包括将对 suspendCancellableCoroutine
的调用移到登录检查之外,将 showLoginForm
的内容包装在 withContext( Dispatchers.IO)
,使用 coroutineScope.launch(newSingleThreadContext("MyOwnThread")
等。我从互联网上阅读的印象是,这是一个有效的用例。我是什么做错了吗?
最佳答案
首先,您误解了挂起
函数的概念。调用函数 showLoginForm()
确实不会启动新的协程。单个协程中的代码始终按顺序执行 - 首先调用 showLoginForm()
,它会延迟,不会恢复任何继续,因为 loginContinuation
为 null
code>,然后 suspendCancellableCoroutine
永远挂起你的协程并导致死锁。
启动一个执行 showLoginForm()
的新协程可以使您的代码正常工作:
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
launch {
showLoginForm()
}
suspendCancellableCoroutine<Unit> {
loginContinuation = it
}
}
// call the api or whatever
delay(1000)
println("comment posted!")
}
此代码仍然可能会失败 (*),但在这种特殊情况下不会失败。此代码的工作版本如下所示:
import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main(args: Array<String>) {
println("Hello, world!")
runBlocking {
postComment()
}
}
var isLoggedIn = false
suspend fun CoroutineScope.postComment() {
if (!isLoggedIn) {
suspendCancellableCoroutine<Unit> { continuation ->
launch {
showLoginForm(continuation)
}
}
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm(continuation: CancellableContinuation<Unit>) {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
continuation.resume(Unit) { println("login cancelled") }
}
此外,在您的示例中不需要挂起协程。如果我们可以在同一个协程中执行它的代码,为什么我们还需要另一个协程呢?无论如何,我们需要等到它完成。由于协程按顺序执行代码,因此只有在 showLoginForm()
完成后,我们才会转到 if
分支之后的代码:
var isLoggedIn = false
suspend fun postComment() {
if (!isLoggedIn) {
showLoginForm()
}
delay(1000)
println("comment posted!")
}
suspend fun showLoginForm() {
println("show login form")
delay(1000)
println("delay over")
isLoggedIn = true
}
此方法最适合您的示例,其中所有代码都是连续的。
(*) - 如果在 showLoginForm
完成后调用 suspendCancellableCoroutine
,此代码仍然可能导致死锁 - 例如,如果您删除 delay
调用在 showLoginForm
中或者如果您使用多线程调度程序 - 在 JVM 中,不能保证 suspendCancellableCoroutine
会早于 showLoginForm
被调用。此外,loginContinuation 不是 @Volatile,因此使用多线程调度程序,代码也可能因可见性问题而失败 - 执行 showLoginForm 的线程可能会观察到 loginContinuation
为null
。
关于asynchronous - Kotlin 延续未恢复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60278108/