Kotlin协程:runBlocking与coroutineScope

ryoqjall  于 7个月前  发布在  Kotlin
关注(0)|答案(3)|浏览(93)

我正在尝试使用Kotlin协程,结果遇到了一个我不理解的情况。假设我有两个suspend函数:

suspend fun coroutine() {
    var num = 0
    coroutineScope {
        for (i in 1..1000) {
            launch {
                delay(10)
                num += 1
            }
        }
    }
    println("coroutine: $num")
}

字符串
以及:

suspend fun runBlocked() = runBlocking {
    var num = 0
    for (i in 1..1000) {
        launch {
            delay(10)
            num += 1
        }
    }
    println("Run blocking: $num")
}


然后从main()方法调用它们:

suspend fun main() {
    coroutine()
    runBlocked()
}


coroutine()方法(如预期的那样)打印的数字几乎从来不是1000(通常在970和999之间)。
我不明白的是为什么runBlocked()函数总是打印0。
共程:998
runBlocked:0
我又试了一次,这次做了一个类似于runBlocked()的函数,不同的是这次该方法返回一个值而不是打印:

suspend fun runBlockedWithReturn(): Int = runBlocking {
    var num = 0
    for (i in 1..1000) {
        launch {
            delay(10)
            num += 1
        }
    }
    return@runBlocking num
}


然后我从main()方法调用它:

suspend fun main() {
    val result = runBlockedWithReturn()
    println("Run blocking with return: $result")
}


...但该方法返回0。
为什么会这样?我如何修复runBlocked()方法来打印一个接近1000的数字而不是0?我错过了什么?

kiayqfof

kiayqfof1#

runBlocking从一开始就不能被协程调用。因为我们把它放在一个挂起函数中违反了这个约定,所以我们对它为什么会这样做的任何解释在不同的平台上或将来可能会有所不同。
除此之外,阻塞代码永远不应该在协程中调用,除非您在使用可以处理它的调度器的CoroutineContext中,如Dispatchers.IO
也就是说,发生这种情况的原因是coroutineScope挂起,直到它的所有子协程完成,而runBlocking没有。您从runBlocking启动协程,然后立即返回,所以当您从函数返回num时,这些协程都没有必要有机会甚至开始运行。
如果你想等待所有在runBlocking lambda中启动的协程,你需要join()每个协程。你可以把它们放在一个列表中,然后在上面调用joinAll()。例如:

(1..1000).map {
    launch {
        delay(10)
        num += 1
    }
}.joinAll()

字符串
但是,runBlocking永远不应该在协程中被调用,我只是描述了如果你从协程外部使用runBlocking来实现它的预期目的,在非协程和协程代码之间建立桥梁,你会如何做。

rjee0c15

rjee0c152#

你不应该从suspend fun内部调用runBlocking
在任何情况下,在从runBlocking返回一个值之前,您都不会等待启动的协程完成,但是使用coroutineScope会强制在该函数中启动的协程在返回之前完成。

eaf3rand

eaf3rand3#

现有的答案集中在我们不应该做什么,但我认为他们错过了一点,所以我们看到的差异的主要原因。
coroutineScope()runBlocking()都保证在退出代码块后,里面的所有协程都已经完成运行。但是出于某种原因,我不知道是不是故意的,你写的两种情况不同。在coroutine()的例子中,你把println()放在coroutineScope()块下面,所以它保证在所有子块之后运行。另一方面,在runBlocked()中,你把println()放在runBlocking()里面,这样它就可以并发地运行到子节点。只要用类似于coroutine()的方式重写你的runBlocked(),所以把println()放在runBlocking()下面,你就会看到1000,正如你所期望的那样。
这两个例子的另一个区别是,默认情况下,runBlocking()使用单个线程运行,而coroutineScope()可以使用多个线程。因此,coroutineScope()产生一个随机值,这是不安全共享可变状态的结果。runBlocking()更可预测。如果println()在其中,它总是产生0,因为println()在任何子进程之前运行。或者如果println()低于runBlocking(),它总是产生1000,因为子进程实际上是一次运行一个,它们不会并行修改值。

相关问题