我正在读Coroutine基础,试着去理解和学习它。
下面的代码有一个部分:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(900L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // This line will be printed before nested launch
}
println("Coroutine scope is over") // This line is not printed until nested launch completes
}输出结果如下:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over我的问题是为什么这句话:
println("Coroutine scope is over") // This line is not printed until nested launch completes永远是最后一个吗?
它不应该被称为:
coroutineScope { // Creates a new coroutine scope
....
}被停职了?
还有一份说明:
runBlocking和coroutineScope的主要区别在于,后者在等待所有子线程完成时不会阻塞当前线程。
我不明白coroutineScope和runBlocking在这里有什么不同?coroutineScope看起来像它的阻塞,因为它只有在完成时才到达最后一行。
有人能在这里启发我吗?
提前谢谢。
发布于 2018-11-29 10:20:40
我不明白coroutineScope和runBlocking在这里有什么不同?coroutineScope看起来像它的阻塞,因为它只有在完成时才到达最后一行。
有两个独立的世界:可悬置的世界(在一个协同线内)和不可悬念的世界。一旦您进入runBlocking的主体,您就进入了可挂起的世界,在这种情况下,suspend fun的行为就像阻塞代码,在suspend fun返回之前,您无法进入下一行。coroutineScope是一个suspend fun,只有当它中的所有协同线都完成时才返回。因此,最后一行必须打印在末尾。
我从一条评论中抄袭了上面的解释,这条评论似乎引起了读者的注意。这是最初的答案:
从代码块的角度来看,您的理解是正确的。runBlocking和coroutineScope之间的区别发生在一个较低的级别:当协同线被阻塞时,线程发生了什么?
runBlocking不是suspend fun。调用它的线程一直在内部,直到协同线完成为止。coroutineScope是suspend fun。如果您的协同线挂起,coroutineScope函数也会被挂起。这允许顶层函数(创建协同线的非挂起函数)继续在同一线程上执行。线程已经“转义”了coroutineScope块,并准备做一些其他工作。在您的特定示例中:当您的coroutineScope挂起时,控件返回到runBlocking中的实现代码。此代码是一个事件循环,它驱动您在其中启动的所有协同工作。在您的例子中,会有一些协同机制在延迟后运行。当时间到了,它将恢复适当的协同线,它将运行一段时间,挂起,然后控制将再次在runBlocking内。
虽然上面描述了概念上的相似之处,但它也应该向您展示runBlocking是一个与coroutineScope完全不同的工具。
runBlocking是一个低级别的构造,只用于框架代码或像您这样的独立示例中。它将一个现有线程转换为一个事件循环,并使用一个Dispatcher创建它的协同线,该将恢复的协同线发布到事件循环的队列中。coroutineScope是面向用户的构造,用于描述任务内部并行分解的边界。您可以使用它方便地等待发生在其中的所有async工作,获得最终结果,并在一个中心位置处理所有故障。发布于 2019-12-03 08:16:20
所选的答案是好的,但未能解决所提供的示例代码的其他一些重要方面。例如,启动是非阻塞的,应该立即执行。这根本不是事实。启动本身立即返回,但启动内的代码似乎被放入队列中,并且只有在以前放入队列的任何其他启动完成时才执行。
下面是一个类似的示例代码,删除了所有延迟,还包括了一个额外的启动。不看下面的结果,看看是否可以预测数字的打印顺序。很有可能你会失败:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
coroutineScope {
launch {
println("2")
}
println("3")
}
coroutineScope {
launch {
println("4")
}
println("5")
}
launch {
println("6")
}
for (i in 7..100) {
println(i.toString())
}
println("101")
}结果是:
3
1
2
5
4
7
8
9
10
...
99
100
101
6数字6是最后打印出来的,即使在经过了将近100个println之后也是如此,这一事实表明,上一次启动中的代码在启动完成后所有非阻塞代码都不会被执行。但这也不是真的,因为如果是这样的话,第一次发射应该在数字7到101完成之前不会执行。底线?混合启动和coroutineScope是高度不可预测的,如果您期望执行的方式有一定的顺序,就应该避免。
要证明启动内部的代码被放置到队列中,并且只有在所有非阻塞代码完成后才执行,请运行此代码(不使用coroutineScopes ):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
launch {
println("2")
}
launch {
println("3")
}
for (i in 4..100) {
println(i.toString())
}
println("101")
}这就是你得到的结果:
4
5
6
...
101
1
2
3添加一个CoroutineScope将破坏此行为。它将导致CoroutineScope后面的所有非阻塞代码在CoroutineScope完成之前不被执行。
还应该注意的是,在此代码示例中,队列中的每一个启动都按照添加到队列中的顺序顺序执行,每次启动只在上一次启动执行之后执行。这可能会使所有启动看起来都有一个共同的线程。这不是真的。他们每个人都有自己的线索。但是,如果启动中的任何代码调用挂起函数,则在执行挂起函数时立即启动队列中的下一个启动。老实说,这是非常奇怪的行为。为什么不异步运行队列中的所有启动呢?虽然我不知道这个队列中发生了什么,但我的猜测是,队列中的每个启动都没有自己的线程,而是共享一个公共线程。只有当遇到挂起函数时,才会出现为队列中的下一个启动创建一个新线程。这样做可以节省资源。
总之,执行按以下顺序进行:
发布于 2020-01-21 02:39:57
runBlocking是用来阻塞主线程的。
coroutineScope是用来阻止runBlocking的。
https://stackoverflow.com/questions/53535977
复制相似问题