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

Ami*_*iri 4 async-await kotlin kotlin-coroutines

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

运行阻塞

不应在协程中使用此函数。它旨在将常规阻塞代码桥接到以挂起风格编写的库,以用于main函数和测试。

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

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

所以在这种情况下似乎应该使用runBlocking 。

但是,我认为我应该完全理解上下文的主题,以查看在这种情况下哪些上下文最适合传递给 runBlocking:

return runBlocking(???????) {
    job1.await() + job2.await()
}
Run Code Online (Sandbox Code Playgroud)

编辑:很明显,我对这个问题的措辞很糟糕,因为所有回答它的尝试都错过了我提出的实际问题和约束。所以让我们尝试不同的方法......

这有效:

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
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:无论如何我可以达到相同的结果:

  1. 如果没有使用runBlocking()在所有。
  2. 如果没有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
}
Run Code Online (Sandbox Code Playgroud)

我有一个小的实用程序方法,它运行任何给定的外部命令并返回其输出(即围绕 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
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码正常工作。但是,它为每个命令执行创建了两个额外的线程。我想通过切换到协程来避免这种情况。我能够做到这一点,但我不得不使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码也有效,但我读到它runBlocking应该只用于main()方法和测试,即不打算以这种方式使用。看看它的实现,它看起来很可怕,而且确实看起来像是人们不想从某个实用程序方法中重复调用的东西。

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

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

Ami*_*iri 6

对于任何与我犯同样错误的未来旅行者 -runBlocking不仅可以在main/ 测试中使用 - 而且还可以:

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

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


Phi*_*fia 5

您可以使用在后台执行操作的调度程序创建自己的范围。如果您想等待某些内容完全执行完毕,可以使用 withContext。

private val myScope = CoroutineScope(Dispatchers.Main) 

myScope.launch {

   withContext(Dispatchers.IO) {
        //to stuff in the background
    }

}

Run Code Online (Sandbox Code Playgroud)

运行下面的代码,您将看到它打印 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为您没有完全理解我的问题。我需要阻塞/非挂起代码来阻塞并等待协程完成,并获取其结果。如果我理解正确的话,您在这里所做的就是演示如何以“即发即忘”的方式从非挂起代码启动协程...... (3认同)