asynchronous - Kotlin 延续未恢复

标签 asynchronous kotlin kotlin-coroutines continuations

我正在尝试了解 suspendCoroutinesuspendCancellableCoroutine。我认为它们在以下情况下可能有用:

  1. 启动协程时,检查用户是否已登录。
  2. 如果没有,请提供凭据并暂停当前正在执行的协程。
  3. 提交凭据后,从暂停的同一行恢复协程。

这可以编译,但永远不会超过“延迟结束”,即继续永远不会恢复:

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(),它会延迟,不会恢复任何继续,因为 loginContinuationnull 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 的线程可能会观察到 loginContinuationnull

关于asynchronous - Kotlin 延续未恢复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60278108/

相关文章:

javascript - 如何在 Node.js 中实现同步执行的要求

android - 如何立即开始执行 Kotlin Coroutine

android - 从协程范围返回值而不使用 runBlocking

python - 为什么选择 react 器模式(Twisted、Gevent、Node.js 等)

c# - C# await 关键字是否会导致函数调用阻塞?

android - Flutter:未处理的异常:MissingPluginException(未找到 channel 上方法的实现)

java - 为什么 Kotlin 在使用 Proxy 时会抛出 IllegalArgumentException

error-handling - 在Kotlin中使用HTTP客户端时如何正确处理错误?

kotlin - 如何从不同的函数发出 Flow 值? Kotlin 协程

jquery - ASP.net 进度条在异步操作期间通过 jQuery ajax 更新