Kotlin:如何在没有运行阻塞的情况下等待来自非挂起的协同路由?

Kotlin:如何在没有运行阻塞的情况下等待来自非挂起的协同路由?,kotlin,async-await,kotlin-coroutines,Kotlin,Async Await,Kotlin Coroutines,编辑2:我想我误解了文档。我读到: 运行阻塞 此函数不应从协同程序中使用。它设计用于将常规阻塞代码连接到以挂起方式编写的库,以便在main函数和测试中使用 这意味着除了用于main或测试之外,我根本不应该使用runBlocking()。但我现在意识到我读错了,特别是这一部分: 它旨在将常规阻塞代码与以挂起方式编写的库连接起来 因此,在这个场景中似乎应该使用runBlocking 然而,我认为我应该充分探讨上下文的主题,看看在这种情况下,哪些上下文最适合传递给runBlocking: return

编辑2:我想我误解了文档。我读到:

运行阻塞

此函数不应从协同程序中使用。它设计用于将常规阻塞代码连接到以挂起方式编写的库,以便在
main
函数和测试中使用

这意味着除了用于
main
或测试之外,我根本不应该使用
runBlocking()
。但我现在意识到我读错了,特别是这一部分:

它旨在将常规阻塞代码与以挂起方式编写的库连接起来

因此,在这个场景中似乎应该使用runBlocking

然而,我认为我应该充分探讨上下文的主题,看看在这种情况下,哪些上下文最适合传递给runBlocking:

return runBlocking(???????) {
    job1.await() + job2.await()
}

编辑:很明显,我的问题措辞很糟糕,因为所有回答问题的尝试都忽略了实际问题和我提出的限制。所以让我们尝试另一种方法

这项工作:

fun doSomething(): Int {
    val job1 = GlobalScope.async { calculateSomething() }
    val job2 = GlobalScope.async { calculateSomething() }
    return runBlocking {
        job1.await() + job2.await()
    }
}

suspend fun calculateSomething(): Int {
    delay(1000L)
    return 13
}

suspend fun calculateSomethingElse(): Int {
    delay(2000L)
    return 19
}
我的问题是:我是否可以达到同样的结果:

  • 不使用
    运行阻塞()
  • 而不将
    doSomething()
    转换为
    suspend
    函数
  • ?

    换句话说:有什么东西可以代替
    来完成以下工作吗

    fun doSomething(): Int {
        val job1 = GlobalScope.async { calculateSomething() }
        val job2 = GlobalScope.async { calculateSomething() }
        return ????????? {
            job1.?????() + job2.?????()
        }
    }
    
    suspend fun calculateSomething(): Int {
        delay(1000L)
        return 13
    }
    
    suspend fun calculateSomethingElse(): Int {
        delay(2000L)
        return 19
    }
    

    我有一个小实用程序方法,它运行任何给定的外部命令并返回其输出(即围绕Java Process API的小包装):

    此代码工作正常。但是,它会为每个命令执行创建两个附加线程。我想通过切换到协同程序来避免这种情况。我能够做到这一点,但我必须使用
    运行阻塞

    class RunningCommand(private val proc: Process) {
    
        fun waitFor(): String {
            proc.outputStream.close()
    
            var output = ""
            val outputRdr = GlobalScope.async { output = proc.inputStream.bufferedReader().use { it.readText() } }
            var error = ""
            val errorRdr = GlobalScope.async { error = proc.errorStream.bufferedReader().use { it.readText() } }
    
            proc.waitFor()
    
            runBlocking {
                outputRdr.await()
                errorRdr.await()
            }
    
            if (proc.exitValue() != 0) {
                throw RuntimeException("Command returned non-zero status: $output${error}")
            }
    
            return output
        }
    }
    
    这段代码也可以工作,但我知道
    runBlocking
    应该只用于
    main()
    方法和测试,也就是说,不应该以这种方式使用。偷看一下它的实现,它看起来很可怕,而且看起来确实像是人们不想从某个实用方法中重复调用的东西

    所以我的问题是:否则我该如何弥合阻塞代码和协同路由之间的鸿沟?或者换句话说,从非
    挂起
    代码等待
    挂起
    功能的正确方法是什么

    或者仅仅是因为我的设计是错误的,为了在任何地方使用协程,我需要使
    main()
    方法
    runBlocking
    基本上总是在某个协程范围内?

    您可以使用在后台执行操作的调度程序创建自己的作用域。如果要等待某个内容完全完成执行,可以使用withContext

    private val myScope = CoroutineScope(Dispatchers.Main) 
    
    myScope.launch {
    
       withContext(Dispatchers.IO) {
            //to stuff in the background
        }
    
    }
    
    
    运行下面的代码,您将看到它打印20,而不是null

    fun main() {
        callSuspendFun()
    }
    
    suspend fun doWorkAndReturnDouble(num: Int): Int {
        delay(1000)
        return num * 2
    }
    
    fun callSuspendFun() {
        val coroutineScope = CoroutineScope(Dispatchers.Main)
        coroutineScope.launch {
            var num: Int? = null
            withContext(Dispatchers.IO) {
                val newNum = 10
                num = doWorkAndReturnDouble(newNum)
            }
            println(num)
        }
    }
    

    因此,要从非挂起函数调用挂起函数,而不使用runBlocking,您必须创建一个协程作用域。在上下文中,您等待代码的执行。

    您应该使用
    coroutineScope

    suspend fun waitFor(): String = coroutineScope {
        proc.outputStream.close()
    
        var output = ""
        val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
        var error = ""
        val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
    
        proc.waitFor()
    
            outputRdr.await()
            errorRdr.await()
    
        if (proc.exitValue() != 0) {
            throw RuntimeException("Command returned non-zero status: $output${error}")
        }
    
        return output
    }
    

    您可以将
    CoroutineScope()
    Dispathers.IO
    一起使用,它将在后台线程中启动一个协程,并将在该线程上卸载您的执行

    class RunningCommand(private val proc: Process) {
    
    fun waitFor(): String {
        // Starting a new coroutine on Background thread (IO)
        proc.outputStream.close()
        var output = ""
        CoroutineScope(Dispatchers.Unconfined).async {
    
            val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
            var error = ""
            val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
    
            proc.waitFor()
    
            // No need of runBlocking 
            // await() call would wait for completion of its async block
            outputRdr.await() // Execution would block until outputRdr block completion on IO thread
            // At this stage, outputRdr block is completed
            errorRdr.await() // Execution would block until errorRdr block completion on IO thread
            // At this stage, errorRdr block is completed
    
    
            if (proc.exitValue() != 0) {
                throw RuntimeException("Command returned non-zero status: $output${error}")
            }
        return@async output
        }
        return output
    }
    
    }
    

    注意:如果您从任何协程上下文调用
    waitFor()
    方法,您可以通过编写
    coroutineScope{}
    而不是
    coroutineScope(Dispatchers.IO)来在同一协程上下文上继续工作。启动{}
    ,正确管理结构化并发

    对于任何未来的旅行者,如果他们犯了与我相同的错误-
    runBlocking
    不仅可以在
    main
    /tests中使用,还可以:

    它的设计目的是将常规阻塞代码连接到以挂起方式编写的库


    不知何故,我给人的印象是,仅用于某些库函数是有害的,但事实并非如此。

    我不确定我是否遵循了-您能否更改示例,以显示如何在不使用
    runBlocking
    的情况下等待非挂起函数的协程或挂起函数?我添加了一个您可以自己运行的示例。希望能有更多帮助。我想你还没有完全理解我的问题。我需要阻塞/非挂起代码来阻塞并等待协同路由完成,然后得到它的结果。如果我理解正确的话,您在这里所做的是演示如何从非挂起代码以“触发并忘记”的方式启动协同程序……这意味着
    waitFor()
    现在是一种
    suspend
    方法
    waitFor()
    是从非
    suspend
    方法调用的。如何使调用方等待
    waitFor()
    的结果?此代码不编译
    waitFor()
    现在不返回任何内容。此外,
    proc.outputStream.close()
    proc.waitFor()
    的行现在生成一个警告“不适当的阻塞方法调用”。此外,不接受return语句,因为启动块的类型为
    CoroutineScope。(->Unit
    。我已更正错误
    proc.outputStream.close()
    发出了阻塞调用的警告,所以我将其放在协同路由范围之外和之上。我还处理了返回值错误,如果您希望从
    waitFor()
    获得正确的
    输出
    ,则需要将此函数挂起。当前,
    输出
    值将与正确的值不匹配,因为
    返回输出
    行未等待启动的协同程序completion@AmirAbiri:我已经编辑了代码以处理该情况。现在,将返回具有正确值的输出。另外,
    waitFor()
    是非挂起函数。请运行此代码。这也不起作用-
    waitFor()
    只会立即返回
    。还请注意,您的原始示例在全局范围内有两个
    async()
    ,存在一个协程问题
    class RunningCommand(private val proc: Process) {
    
    fun waitFor(): String {
        // Starting a new coroutine on Background thread (IO)
        proc.outputStream.close()
        var output = ""
        CoroutineScope(Dispatchers.Unconfined).async {
    
            val outputRdr = async { output = proc.inputStream.bufferedReader().use { it.readText() } }
            var error = ""
            val errorRdr = async { error = proc.errorStream.bufferedReader().use { it.readText() } }
    
            proc.waitFor()
    
            // No need of runBlocking 
            // await() call would wait for completion of its async block
            outputRdr.await() // Execution would block until outputRdr block completion on IO thread
            // At this stage, outputRdr block is completed
            errorRdr.await() // Execution would block until errorRdr block completion on IO thread
            // At this stage, errorRdr block is completed
    
    
            if (proc.exitValue() != 0) {
                throw RuntimeException("Command returned non-zero status: $output${error}")
            }
        return@async output
        }
        return output
    }
    
    }