【问题标题】:How to make sense of Kotlin coroutines?如何理解 Kotlin 协程?
【发布时间】:2019-12-06 04:58:33
【问题描述】:

我已经尝试阅读有关 Kotlin 协程的各种教程和页面,尽管这对我来说有点道理,但我仍然觉得它没有被点击,我还没有准备好开始使用协程编写异步非阻塞代码.我认为我缺少的是一个图表或图片,说明一段协程代码执行时究竟发生了什么以及以什么顺序执行。该代码如何在线程级别运行?

    launch {
        delay(1000)
        println("World (${currentThread().name})")
    }
    println("Hello (${currentThread().name})")
    sleep(1500)

我的理解是这样的。我很高兴得到纠正或给出不同的例子来加深我的理解。

Line0:代码从主线程开始

Line1:在新线程上启动一个新的协程(我想来自 forkjoin 池)

Line2:挂起函数,协程挂起并将线程返回到线程池(因此是非阻塞的)

Line5:在主线程上打印

Line6:阻塞主线程1.5s

Line3:协程恢复执行(不确定这里的哪个线程 - 与挂起之前的线程相同还是可以是不同的线程?)。协程在该线程上打印并完成,因此再次将线程返回到池中。

我的另一个问题是,如果我将整个代码包装在 runBlocking { ... } 周围,低级执行将如何变化

【问题讨论】:

  • 为了更好地了解背后的内容,我建议您观看此InfoQ presentation。它是关于 Oracle 正在进行的工作,旨在提供 Java 中的延续/纤程。从概念上讲,它很像 Kotlin 协同程序。
  • 将“启动”视为 Java 中的传统线程创建会有所帮助,也就是说,它独立于创建它的主线程工作。 runBlocking 表示协程及其创建的所有协程必须首先结束。应用于主程序,这意味着程序不会结束,除非所有生成的协程都完成(与守护程序不同)。
  • @Neil runBlocking 做的远不止这些,它为协程创建了一个运行环境。一旦你已经在协程环境中,甚至不应该使用它。它不会在协程完成之前结束,这只是一个副作用。
  • @MarkoTopolnik 我同意,但在一条评论中解释所有这些有点过分。我想将类似 java 的行为与 kotlin 中较新的协程区分开来,因为他可能来自 Java 背景。
  • @Neil 我明白你的意思。我仍然要指出 launch 通常不会独立于创建它的主线程工作(Android 上的基本习惯是让它在同一个线程上工作),这几乎正是协程的原因与线程如此不同。你必须跳过这个认知圈,接受现在在单个线程上存在并发。这是真正挖掘协程的关键。

标签: asynchronous kotlin async-await kotlin-coroutines


【解决方案1】:

这里是另一个例子:https://kotlinlang.org/docs/reference/coroutines/basics.html#your-first-coroutine

fun main() {
    GlobalScope.launch { // launch a new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

以上代码打印:

Hello,
World!

如您所见,虽然代码看起来是同步的,但执行是异步的。这就是 Coroutine 的全部理念。

【讨论】:

    【解决方案2】:

    您的代码实际上并没有做任何可以揭示协程特殊性质的事情。它使两个线程同时执行它们的操作,就像在普通 Java 中一样。

    只有当你在同一个线程(例如,主线程)上启动协程时,它才会变得有趣。这是您使用 runBlocking 块实现的目标之一:

    runBlocking {
        launch {
            delay(1000)
            println("Hello from the launched coroutine. My thread is "
                    + Thread.currentThread().name)
        }
        println("Hello from the top-level coroutine. My thread is "
                + Thread.currentThread().name)
    }
    

    这将打印出来

    Hello from the top-level coroutine. My thread is main
    Hello from the launched coroutine. My thread is main
    

    runBlocking 在调用线程上运行一个事件循环,并将对它的引用传播到您在其中启动的所有协程。例如,delay(1000) 将向该事件循环发布一个事件,并指定延迟一秒,然后它将暂停协程。这将允许主线程运行launch 下面的其余代码。当时间过去时,事件循环将运行事件处理程序,这将反过来恢复协程。


    一个更具教育意义的例子是启动一个没有任何Dispatcher 的协程。这消除了协程看起来像线程的错觉,并揭示了它们真正的魔力:通过调用continuation.resume(),您可以使当前线程直接跳到挂起的协程代码块的中间,而所有这些都使用普通的 Java 方法调用。我建议研究this answer,这里有详细解释。如果您有兴趣更深入地了解普通 Java 方法如何做到这一点,我建议您在 YouTube 上观看 Roman Elizarov explaining this

    【讨论】:

      猜你喜欢
      • 2022-07-10
      • 1970-01-01
      • 1970-01-01
      • 2020-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-02
      • 2021-09-08
      相关资源
      最近更新 更多