Kotlin:如何在没有runBlocking的情况下等待非暂停的协程?

时间:2019-12-30 09:52:53

标签: kotlin async-await kotlin-coroutines

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

class RunningCommand(private val proc: Process) {

    fun waitFor(): String {
        proc.outputStream.close()

        var output = ""
        val outputRdr = thread { output = proc.inputStream.bufferedReader().use { it.readText() } }
        var error = ""
        val errorRdr = thread { error = proc.errorStream.bufferedReader().use { it.readText() } }

        proc.waitFor()

        outputRdr.join()
        errorRdr.join()

        if (proc.exitValue() != 0) {
            throw RuntimeException("Command returned non-zero status: $output$error")
        }

        return output
    }
}

此代码正常工作。但是,它为每个命令执行创建了两个附加线程。我想通过改用协程避免这种情况。我能够做到这一点,但是我不得不使用runBlocking

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()方法和测试中使用,即不应以这种方式使用。偷看它的实现看起来很可怕,而且确实看起来像是不想从某个实用程序方法中反复调用的东西。

所以我的问题是:我还应该如何弥合阻止代码和协程之间的差距?或者换句话说,从非suspend代码中等待suspend函数的正确方法是什么?

还是仅仅是我的设计是错误的,并且要在任何地方使用协程,我都需要制作main()方法runBlocking,并且基本上总是在某个协程范围内?

4 个答案:

答案 0 :(得分:0)

对于以后犯任何与我相同的错误的旅行者-rubBlocking不仅可以在main /测试中使用-还可以:

它旨在将常规的阻止代码桥接到以挂起样式编写的库中

我以某种方式给人的印象是,仅将某些库函数使用是邪恶的,但事实并非如此。

答案 1 :(得分:-1)

您可以使用在后台执行操作的调度程序创建自己的作用域。如果要等待某些事情完全完成执行,可以使用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,必须创建一个协程作用域。有了withContext,您就可以等待代码的执行。

答案 2 :(得分:-1)

您应该使用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
}

答案 3 :(得分:-1)

您可以将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).launch { }来在相同的协程上下文上进行工作,并正确管理结构化并发。