代码之家  ›  专栏  ›  技术社区  ›  Archie G. Quiñones

协同程序:runBlocking与coroutineScope

  •  9
  • Archie G. Quiñones  · 技术社区  · 6 年前

    我在看书 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
        ....
    }
    

    停赛了吗?

    这里还有一个注释:

    runBlocking和coroutineScope之间的主要区别在于后者在等待所有子线程完成时不会阻塞当前线程。

    我不明白coroutineScope和runBlocking在这里有什么不同?coroutineScope看起来像是它的阻塞,因为它只在完成时到达最后一行。

    这里有人能给我点化一下吗?

    2 回复  |  直到 5 年前
        1
  •  62
  •   Marko Topolnik    6 年前

    我不明白coroutineScope和runBlocking在这里有什么不同?coroutineScope看起来像是它的阻塞,因为它只在完成时到达最后一行。

    runBlocking coroutineScope 发生在较低的级别:当协程被阻塞时线程发生了什么?

    • 不是一个 suspend fun . 调用它的线程将保留在其中,直到协同例程完成。

    • 共线镜 是一个 暂停娱乐 . 如果您的协同程序暂停,则 共线镜 不悬浮 创建协同例程的函数,以继续在同一线程上执行。线程已经“逃逸”了 共线镜 块,并准备做一些其他工作。

    共线镜 运行阻塞 . 这段代码是一个事件循环,它驱动您在其中启动的所有协程。在您的情况下,将有一些协同程序计划在延迟后运行。当时间到达时,它将恢复适当的协同程序,该程序将运行一段时间,挂起,然后控制将再次进入 运行阻塞 .


    虽然上面描述了概念上的相似性,但也应该向您展示 运行阻塞 是一个 完全不同 工具来自 共线镜

    • 运行阻塞 是一个低级构造,仅用于框架代码或像您这样的自包含示例中。它将现有线程转换为事件循环,并使用 Dispatcher 将恢复协同路由发布到事件循环的队列。

    • 共线镜 是一个面向用户的构造,用于描述正在并行分解的任务的边界。您可以使用它方便地等待所有 async 在它内部进行工作,获得最终结果,并在一个中心位置处理所有故障。

        2
  •  32
  •   Johann    5 年前

    选择的答案很好,但未能解决所提供示例代码的其他一些重要方面。例如,启动是非阻塞的,并且应该立即执行。这根本不是事实。启动本身会立即返回,但启动中的代码看起来确实被放入了队列中,并且只有在之前放入队列的任何其他启动完成时才会执行。

    下面是一段类似的示例代码,删除了所有延迟,并包含了一个额外的启动。不看下面的结果,看看是否可以预测数字的打印顺序。你很可能会失败:

    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
    

    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之后的所有非阻塞代码。

    还应该注意,在这个代码示例中,队列中的每个启动都是按照它们添加到队列中的顺序依次执行的,并且每个启动将仅在前一次启动执行之后执行。这可能会使所有启动看起来共享一个公共线程。事实并非如此。他们每个人都有自己的线索。但是,如果启动中的任何代码调用挂起函数,则在执行挂起函数时,队列中的下一次启动将立即启动。老实说,这是非常奇怪的行为。为什么不异步运行队列中的所有启动?虽然我不知道队列中发生了什么,但我猜队列中的每个启动都没有自己的线程,而是共享一个公共线程。只有在遇到挂起函数时,才会为队列中的下一次启动创建新线程。这样做可以节省资源。

    总之,执行顺序如下:

    1. 启动中的代码放在队列中,并按添加顺序执行。
    2. 启动后的非阻塞代码在队列中的任何内容执行之前立即执行。
    3. 协同路由作用域会阻止它后面的所有代码,但会在恢复到协同路由作用域后面的代码之前执行队列中的所有启动协同路由。
        3
  •  14
  •   fung    4 年前

    coroutineScope用于阻止运行阻塞。

        4
  •  7
  •   nyarian    6 年前

    runBlocking 只是 阻碍 当前线程,直到内部协程完成。这里是执行的线程 运行阻塞 来自 coroutineScope 就要完成了。

    弗斯特 launch 只是不允许线程执行后面的指令 运行阻塞 发射 街区——这就是为什么 Task from coroutine scope 打印时间比 Task from runBlocking .

    但是嵌套的 共线镜 运行阻塞 不允许线程执行在此之后的指令 运行阻塞 将阻止线程,直到从 共线镜 Coroutine scope is over Task from nested launch .

        5
  •  2
  •   Bahaa    3 年前

    好吧,在阅读了这里的所有答案之后,我发现除了重复文档片段的措辞之外,没有人回答这个问题。

    因此,我继续在别处寻找答案,并找到了它 here . 它实际上显示了不同的行为 coroutineScope runBlocking (即暂停和阻塞之间的差异)

        6
  •  1
  •   onmyway133    5 年前

    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()
    }
    

    使用runBlocking时,我们没有使用结构化并发,因此f的调用可能会失败,而所有其他执行都会继续进行。而且,我们对代码的其余部分也没有很好地处理。通过使用runBlocking,我们强制阻塞线程,直到pmap的整个执行完成,而不是让调用方决定执行的方式。