协程:runBlocking 与 coroutineScope
Coroutines: runBlocking vs coroutineScope
我正在阅读 Coroutine Basics 试图理解和学习它。
那里有一段代码:
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
....
}
暂停了吗?
那里还有一条注释:
The main difference between runBlocking and coroutineScope is that the latter does not block the current thread while waiting for all children to complete.
我不明白 coroutineScope 和 runBlocking 在这里有什么不同? coroutineScope 看起来像是阻塞,因为它只在完成后到达最后一行。
这里有谁能赐教吗?
提前致谢。
runBlocking
只是 阻塞 当前线程,直到内部协程完成。在这里,执行 runBlocking
的线程将被 阻塞,直到 来自 coroutineScope
的协程完成。
首先 launch
不允许线程执行 runBlocking
之后的指令,但会允许继续执行此 launch
块之后的指令 - 这就是为什么Task from coroutine scope
在 Task from runBlocking
.
之前打印
但是嵌套在 runBlocking
上下文中的 coroutineScope
将不允许线程执行此 coroutineScope
块之后的指令,因为 runBlocking
会阻塞线程,直到来自 coroutineScope
的协程完全完成。这就是为什么 Coroutine scope is over
总是在 Task from nested launch
之后。
I don't understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
有两个独立的世界:suspendable 世界(在协程中)和 non-suspendable 世界。一旦你进入 runBlocking
的主体,你就进入了可暂停的世界,其中 suspend fun
s 的行为就像阻塞代码,你无法进入下一行,直到 suspend fun
returns。 coroutineScope
是一个 suspend fun
只有当它里面的所有协程都完成时才 returns 。因此最后一行必须打印在最后。
上面的解释是我从一个评论里抄下来的,好像很受读者欢迎。以下是原答案:
从block中的代码来看,你的理解是正确的。 runBlocking
和 coroutineScope
之间的区别发生在较低级别:协程被阻塞时线程发生了什么?
runBlocking
不是 suspend fun
。调用它的线程保留在其中,直到协程完成。
coroutineScope
是一个 suspend fun
。如果您的协程挂起,coroutineScope
函数也会挂起。这允许顶层函数,一个创建协程的 非挂起 函数,继续在同一个线程上执行。线程已经“逃脱”了 coroutineScope
块并准备好做一些其他的工作。
在你的具体例子中:当你的coroutineScope
挂起时,控制returns到runBlocking
里面的实现代码。此代码是一个事件循环,它驱动您在其中启动的所有协程。在您的情况下,将有一些协程在延迟后安排到 运行。时间到了,它会恢复相应的协程,协程会运行一小会儿,挂起,然后控制又会在runBlocking
.
里面
虽然上面描述了概念上的相似之处,但它还应向您表明 runBlocking
是一个 与 coroutineScope
完全不同的 工具。
runBlocking
是一个低级构造,仅用于框架代码或像您这样的独立示例。它将一个现有的线程变成一个事件循环,并使用 Dispatcher
创建它的协程,将恢复协程发布到事件循环的队列中。
coroutineScope
是一个面向用户的构造,用于描绘在其中并行分解的任务的边界。您可以使用它方便地等待其中发生的所有 async
工作,获得最终结果,并在一个中心位置处理所有故障。
来自这篇精彩的文章https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
With runBlocking, we were not using Structured Concurrency, so an invocation of f could fail and all other executions would continue unfazed. And also we were not playing nice with the rest of the code. By using runBlocking we were forcefully blocking the thread until the whole execution of pmap finishes, instead of letting the caller decide how the execution should go.
选择的答案很好,但未能解决所提供示例代码的其他一些重要方面。例如,launch 是非阻塞的并且应该立即执行。那明显是错的。启动本身 returns 立即启动,但启动中的代码似乎已放入队列中,并且仅在之前放入队列中的任何其他启动完成时才执行。
这是一段类似的示例代码,其中删除了所有延迟并包括了额外的启动。不看下面的结果,看看你能不能预测出数字的打印顺序。失败的可能性是:
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
即使在执行了将近 100 次 println 之后,最后打印数字 6 的事实表明,在启动完成后所有非阻塞代码之前,最后一次启动中的代码永远不会执行。但这也不是真的,因为如果是这样的话,第一次发射不应该在 7 到 101 号完成之前执行。底线?混合使用 launch 和 coroutineScope 是高度不可预测的,如果您期望执行事情的方式有一定的顺序,则应该避免。
为了证明 launch 中的代码被放入队列并且仅在所有非阻塞代码完成后执行,运行 这个(没有使用 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 之前的所有代码完成之前不被执行。
还应该注意的是,在此代码示例中,队列中的每个启动都是按照它们添加到队列的顺序依次执行的,并且每个启动只会在前一个启动执行之后执行。这可能会使所有发布看起来都共享一个公共线程。这不是真的。他们每个人都有自己的线程。但是,如果启动中的任何代码调用挂起函数,则队列中的下一个启动将在执行挂起函数时立即启动。老实说,这是非常奇怪的行为。为什么不 运行 异步队列中的所有启动?虽然我不知道这个队列中发生的事情的内部结构,但我的猜测是队列中的每个启动都没有自己的线程,而是共享一个公共线程。只有当遇到挂起函数时,才会在队列中为下一次启动创建一个新线程。这样做可能会节省资源。
总而言之,执行是按以下顺序完成的:
- 启动中的代码放置在队列中,并按照添加的顺序执行。
- 启动后的非阻塞代码会在队列中的任何内容执行之前立即执行。
- CoroutineScope 会阻止它后面的所有代码,但会在恢复到 CoroutineScope 后面的代码之前执行队列中的所有启动协程。
runBlocking 用于阻塞主线程。
coroutineScope 是给你阻塞runBlocking的。
好吧,在阅读了这里的所有答案后,我发现其中 none 除了重复文档片段的措辞之外还回答了问题。
所以,我继续在别处寻找答案并找到了 here。它实际上显示了 coroutineScope
和 runBlocking
的行为差异(即暂停和阻塞之间的差异)
我正在阅读 Coroutine Basics 试图理解和学习它。
那里有一段代码:
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
....
}
暂停了吗?
那里还有一条注释:
The main difference between runBlocking and coroutineScope is that the latter does not block the current thread while waiting for all children to complete.
我不明白 coroutineScope 和 runBlocking 在这里有什么不同? coroutineScope 看起来像是阻塞,因为它只在完成后到达最后一行。
这里有谁能赐教吗?
提前致谢。
runBlocking
只是 阻塞 当前线程,直到内部协程完成。在这里,执行 runBlocking
的线程将被 阻塞,直到 来自 coroutineScope
的协程完成。
首先 launch
不允许线程执行 runBlocking
之后的指令,但会允许继续执行此 launch
块之后的指令 - 这就是为什么Task from coroutine scope
在 Task from runBlocking
.
但是嵌套在 runBlocking
上下文中的 coroutineScope
将不允许线程执行此 coroutineScope
块之后的指令,因为 runBlocking
会阻塞线程,直到来自 coroutineScope
的协程完全完成。这就是为什么 Coroutine scope is over
总是在 Task from nested launch
之后。
I don't understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
有两个独立的世界:suspendable 世界(在协程中)和 non-suspendable 世界。一旦你进入 runBlocking
的主体,你就进入了可暂停的世界,其中 suspend fun
s 的行为就像阻塞代码,你无法进入下一行,直到 suspend fun
returns。 coroutineScope
是一个 suspend fun
只有当它里面的所有协程都完成时才 returns 。因此最后一行必须打印在最后。
上面的解释是我从一个评论里抄下来的,好像很受读者欢迎。以下是原答案:
从block中的代码来看,你的理解是正确的。 runBlocking
和 coroutineScope
之间的区别发生在较低级别:协程被阻塞时线程发生了什么?
runBlocking
不是suspend fun
。调用它的线程保留在其中,直到协程完成。coroutineScope
是一个suspend fun
。如果您的协程挂起,coroutineScope
函数也会挂起。这允许顶层函数,一个创建协程的 非挂起 函数,继续在同一个线程上执行。线程已经“逃脱”了coroutineScope
块并准备好做一些其他的工作。
在你的具体例子中:当你的coroutineScope
挂起时,控制returns到runBlocking
里面的实现代码。此代码是一个事件循环,它驱动您在其中启动的所有协程。在您的情况下,将有一些协程在延迟后安排到 运行。时间到了,它会恢复相应的协程,协程会运行一小会儿,挂起,然后控制又会在runBlocking
.
虽然上面描述了概念上的相似之处,但它还应向您表明 runBlocking
是一个 与 coroutineScope
完全不同的 工具。
runBlocking
是一个低级构造,仅用于框架代码或像您这样的独立示例。它将一个现有的线程变成一个事件循环,并使用Dispatcher
创建它的协程,将恢复协程发布到事件循环的队列中。coroutineScope
是一个面向用户的构造,用于描绘在其中并行分解的任务的边界。您可以使用它方便地等待其中发生的所有async
工作,获得最终结果,并在一个中心位置处理所有故障。
来自这篇精彩的文章https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
With runBlocking, we were not using Structured Concurrency, so an invocation of f could fail and all other executions would continue unfazed. And also we were not playing nice with the rest of the code. By using runBlocking we were forcefully blocking the thread until the whole execution of pmap finishes, instead of letting the caller decide how the execution should go.
选择的答案很好,但未能解决所提供示例代码的其他一些重要方面。例如,launch 是非阻塞的并且应该立即执行。那明显是错的。启动本身 returns 立即启动,但启动中的代码似乎已放入队列中,并且仅在之前放入队列中的任何其他启动完成时才执行。
这是一段类似的示例代码,其中删除了所有延迟并包括了额外的启动。不看下面的结果,看看你能不能预测出数字的打印顺序。失败的可能性是:
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
即使在执行了将近 100 次 println 之后,最后打印数字 6 的事实表明,在启动完成后所有非阻塞代码之前,最后一次启动中的代码永远不会执行。但这也不是真的,因为如果是这样的话,第一次发射不应该在 7 到 101 号完成之前执行。底线?混合使用 launch 和 coroutineScope 是高度不可预测的,如果您期望执行事情的方式有一定的顺序,则应该避免。
为了证明 launch 中的代码被放入队列并且仅在所有非阻塞代码完成后执行,运行 这个(没有使用 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 之前的所有代码完成之前不被执行。
还应该注意的是,在此代码示例中,队列中的每个启动都是按照它们添加到队列的顺序依次执行的,并且每个启动只会在前一个启动执行之后执行。这可能会使所有发布看起来都共享一个公共线程。这不是真的。他们每个人都有自己的线程。但是,如果启动中的任何代码调用挂起函数,则队列中的下一个启动将在执行挂起函数时立即启动。老实说,这是非常奇怪的行为。为什么不 运行 异步队列中的所有启动?虽然我不知道这个队列中发生的事情的内部结构,但我的猜测是队列中的每个启动都没有自己的线程,而是共享一个公共线程。只有当遇到挂起函数时,才会在队列中为下一次启动创建一个新线程。这样做可能会节省资源。
总而言之,执行是按以下顺序完成的:
- 启动中的代码放置在队列中,并按照添加的顺序执行。
- 启动后的非阻塞代码会在队列中的任何内容执行之前立即执行。
- CoroutineScope 会阻止它后面的所有代码,但会在恢复到 CoroutineScope 后面的代码之前执行队列中的所有启动协程。
runBlocking 用于阻塞主线程。
coroutineScope 是给你阻塞runBlocking的。
好吧,在阅读了这里的所有答案后,我发现其中 none 除了重复文档片段的措辞之外还回答了问题。
所以,我继续在别处寻找答案并找到了 here。它实际上显示了 coroutineScope
和 runBlocking
的行为差异(即暂停和阻塞之间的差异)