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

  • 启动协程时,请检查用户是否已登录。
  • 如果不是,请提供凭据并暂停当前执行的协程。
  • 提交凭据后,请从挂起它的同一行恢复协程。

  • 这样可以编译,但绝不会使其超出“延迟时间”,即,延续不会恢复:
    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")等。通过阅读互联网,我得到的印象是这是一种有效的用法案件。我究竟做错了什么?

    最佳答案

    绕过Continuations很杂乱,很容易导致您遇到错误...一个函数甚至在将continuation分配给continuation属性之前就完成了。

    由于您想将登录表单转换为暂停函数,因此应该在其中使用suspendCoroutinesuspendCoroutine是您应该放低的低级代码,这样您的主程序逻辑可以使用易于读取的顺序协程,而无需嵌套的launch / suspendCoroutine调用。

    var isLoggedIn = false
    
    suspend fun postComment() {
        if (!isLoggedIn) {
            showLoginForm()
        }
    
        println("is logged in: $isLoggedIn")
    
        if (isLoggedIn) {
            // call the api or whatever
            delay(1000)
            println("comment posted!")
        }
    }
    
    suspend fun showLoginForm(): Unit = suspendCancellableCoroutine { cont ->
        println("Login or leave blank to cancel:")
    
        //Simulate user login or cancel with console input
        val userInput = readLine()
        isLoggedIn = !userInput.isNullOrBlank()
        cont.resume(Unit)
    }
    

    我没有在delay()中使用showLoginForm(),因为您无法在suspendCancellableCoroutine块中调用暂停函数。最后三行也可以包装在scope.launch中,并使用delay而不是readLine,但是实际上,您的UI交互总不是一个协程,不会有延迟。

    编辑:

    试图将延续传递给另一个Activity会特别麻烦。 Google甚至不建议在一个应用程序中使用多个 Activity ,因为很难在它们之间传递对象。要使用Fragments进行操作,您可以编写LoginFragment类以具有如下这样的私有(private)延续属性:
    class LoginFragment(): Fragment {
    
        private val continuation: Continuation<Boolean>? = null
        private var loginComplete = false
    
        suspend fun show(manager: FragmentManager, @IdRes containerViewId: Int, tag: String? = null): Boolean = suspendCancelableCoroutine { cont ->
            continuation = cont
            retainInstance = true
            manager.beginTransaction().apply {
                replace(containerViewId, this@LoginFragment, tag)
                addToBackStack(null)
                commit()
            }
        }
    
        // Call this when login is complete:
        private fun onLoginSuccessful() {
            loginComplete = true
            activity?.fragmentManager?.popBackStack()
        }
    
        override fun onDestroy() {
            super.onDestroy()
            continuation?.resume(loginComplete)
        }
    }
    

    然后,您将显示来自另一个片段的该片段,如下所示:
    lifecycleScope.launch {
        val loggedIn = LoginFragment().show(requireActivity().fragmentManager, R.id.fragContainer)
        // respond to login state here
    }
    

    只要您使用的是Fragment的lifecycleScope而不是Activity的lifecycleScope,并且第一个Fragment也使用retainInstance = true,我认为您应该可以避免屏幕旋转。但是我还没有自己做。

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

    10-12 03:20