【问题标题】:Why Kotlin coroutines run in the same thread sequentially?为什么 Kotlin 协程按顺序在同一个线程中运行?
【发布时间】:2019-04-15 13:29:04
【问题描述】:

我认为使用launch 从协程上下文调用“挂起”函数会使调用异步。但是在下面的示例中,我看到 placeOrder 方法的 2 次调用并没有在同一个线程中一个接一个地运行。 我的错误是什么?

import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File

fun main() = runBlocking {
    t("1")
    launch {
        t("2")
        placeOrder("C:\\Users")
        t("3")
    }
    launch {
        t("12")
        placeOrder("C:\\Program Files")
        t("13")
    }
    t("4")
}


fun t(s: String) {
    val currentThread = Thread.currentThread()
    println(s + ": " + currentThread.name + " " +     currentThread.id)
}

suspend fun placeOrder(d:String): String {
    t("placeOrder $d")
    val user = createUser(d) // asynchronous call to user service
    val order = createOrder(user) // asynchronous call to order service
    t("placeOrder $d finished")
    return order
}

suspend fun createUser(d:String): String {
    t("createUser $d")
    val toString = File(d).walk().map {
        it.length()
    }.sum().toString()
    t("createUser $d finished")
    return toString
}

suspend fun createOrder(user: String): String {
    t("createOrder $user")
    val toString = File("C:\\User").walk().map {
        it.length()
    }.sum().toString()
    t("createOrder $user finished")
    return toString
}

输出:

1: main 1
4: main 1
2: main 1
placeOrder C:\Users: main 1
createUser C:\Users: main 1
createUser C:\Users finished: main 1
createOrder 1094020270277: main 1
createOrder 1094020270277 finished: main 1
placeOrder C:\Users finished: main 1
3: main 1
12: main 1
placeOrder C:\Program Files: main 1
createUser C:\Program Files: main 1
createUser C:\Program Files finished: main 1
createOrder 5651227104: main 1
createOrder 5651227104 finished: main 1
placeOrder C:\Program Files finished: main 1
13: main 1

【问题讨论】:

  • 你不是在使用两个启动调用吗?
  • @StavroXhardha 是的,我认为我可以以这种方式启动 2 次并行执行,但它不能以这种方式工作。为问题添加了输出
  • 你能添加prinrln语句的输出吗?
  • @JohnMercier 添加了

标签: kotlin kotlin-coroutines


【解决方案1】:

您没有编写可挂起的 IO,而是编写了阻塞 IO:

File(d).walk().map {
    it.length()
}

您的函数实际上从未挂起,而是阻塞了与其runBlocking 调度程序关联的单个线程。

你没有给你的协程并发执行的机会。

如果您在上面的代码周围应用withContext(IO) { ... },您将获得并发,但在普通的 Java 类型中,多个线程在 IO 操作中被一起阻塞。

【讨论】:

  • 什么是可挂起 I/O 的例子?
  • 对于文件 IO,我没有任何示例。即使在低操作系统级别,文件 IO 也会阻塞。对于网络 IO,使用任何允许您提交获取请求结果的回调的异步 API。
  • 我正在使用 Vertx,他们有一个带有回调的 http 客户端。但是如果下一个协程需要它的结果来运行,这个协程会是什么样子呢?
  • 您按照this simple idiom 将每个网络调用实现为挂起功能。之后,组合它们就变得微不足道了——只需将它们作为常规函数调用,并将一个的结果传递给下一个。
【解决方案2】:

这种行为的原因有两个:

  1. 所有协程都在runBlocking 范围内执行,这是一个单线程事件循环。所以这意味着除非指定了不同的上下文,否则只使用一个线程。 (以launch(Dispatchers.IO) 为例)
  2. 即使这样,协程也有可能交错,除非您的协程确实调用了实际上必须暂停的暂停函数。这意味着它实际上是一个正常的顺序函数调用。如果您的函数包含 yield()delay(..) 调用,您会看到协同程序在执行中交错。

【讨论】:

  • 那么写一个可挂起方法的正确方法是什么,I/O 或 JDBC 是这样的?
  • 就其本质而言,您需要在一个异步 API 上构建它,该 API 会做一些工作,然后在工作完成后向您发送一条消息,以便您恢复自己的代码。 JDBC 本质上不能暂停(似乎 ADBA 是异步替代品),而基于 java.nio.channels.* 构建的 API 可以用于文件操作。
【解决方案3】:

启动函数签名:

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job (source)

根据 Kotlin 官方文档link

When launch { ... } is used without parameters, it inherits the context (and
thus dispatcher) from the CoroutineScope it is being launched from.

在您的情况下,它继承了在主线程中运行的主 runBlocking 协程的上下文。 由于协程上下文包含一个协程调度程序,它决定了相应的协程使用哪个或哪些线程来执行它,因此您可以为 launch 协程构建器提供不同的 CoroutineContext。例如:

fun main() = runBlocking {
    t("1")
    launch(Dispatchers.Default) {
        t("2")
        placeOrder("C:\\Users")
        t("3")
    }
    launch(Dispatchers.Default) {
        t("12")
        placeOrder("C:\\Program Files")
        t("13")
    }
    t("4")
}

关于挂起函数,挂起函数只是一个普通的 Kotlin 函数,带有一个附加的挂起修饰符,表示该函数可以挂起协程的执行。默认情况下,它不会使调用异步。 您可以使用 Kotlin 的 withContext() 函数通过自定义调度程序(例如 IO 调度程序)执行您的函数代码,如下所示:

suspend fun get(url: String) = withContext(Dispatchers.IO){/* Code for N/W logic */}

这将在与调用协程上下文不同的线程中执行函数体。

这是一个由 3 部分组成的系列博客,解释了 Android 应用程序中协程的使用: https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb

【讨论】:

    【解决方案4】:

    launch 替换为async

    read this

    【讨论】:

      【解决方案5】:

      即使没有runBlocking,上面的代码基本上也是同步运行的!

      由于所有的协程都被启动到 ma​​in 运行在一个单个线程上,最终你可以使用 IO 调度器,它使用 多个线程。

      另外,请注意多个协程可以在一个单个线程上运行,但它们永远不会并行执行,它们可能会因为线程从一个线程切换而显示为并行运行当一个新的协程启动暂停时,协程转移到另一个协程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-28
        • 1970-01-01
        • 2019-12-18
        • 1970-01-01
        • 2021-07-20
        • 2018-10-01
        • 2016-03-20
        相关资源
        最近更新 更多