为什么 Kotlin 协程 运行 在同一个线程中顺序?

Why Kotlin coroutines run in the same thread sequentially?

我认为使用 launch 从协程上下文调用 "suspend" 函数会使调用异步。但是在下面的示例中,我看到 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

你写的不是可暂停的 IO,而是阻塞的 IO:

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

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

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

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

这种行为的原因有两个:

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

启动函数签名:

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

launch 替换为 async

read this

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

由于所有协程都在 main 运行ning 上启动 single 线程,最终您可以使用 IO 使用多个 线程的调度程序。

另请注意,多个协程可以运行在单个线程上,但它们永远不会并行执行,它们可能显示为 运行ning 并行,因为当一个新协程 launchedsuspended 时,线程从一个协程切换到另一个协程。