为什么 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 操作中。
这种行为的原因有两个:
- 你所有的协程都在
runBlocking
范围内执行,这是一个单线程事件循环。所以这意味着除非指定不同的上下文,否则只会使用一个线程。 (以launch(Dispatchers.IO)
为例)
- 即便如此,协程也有可能交错,除非您的协程确实调用了实际上必须暂停的暂停函数。这意味着它实际上是一个正常的顺序函数调用。如果您的函数包含
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
基本上上面的代码是 运行ning 同步 即使没有 runBlocking
!
由于所有协程都在 main 运行ning 上启动 single 线程,最终您可以使用 IO 使用多个 线程的调度程序。
另请注意,多个协程可以运行在单个线程上,但它们永远不会并行执行,它们可能显示为 运行ning 并行,因为当一个新协程 launched 或 suspended 时,线程从一个协程切换到另一个协程。
我认为使用 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 操作中。
这种行为的原因有两个:
- 你所有的协程都在
runBlocking
范围内执行,这是一个单线程事件循环。所以这意味着除非指定不同的上下文,否则只会使用一个线程。 (以launch(Dispatchers.IO)
为例) - 即便如此,协程也有可能交错,除非您的协程确实调用了实际上必须暂停的暂停函数。这意味着它实际上是一个正常的顺序函数调用。如果您的函数包含
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
基本上上面的代码是 运行ning 同步 即使没有 runBlocking
!
由于所有协程都在 main 运行ning 上启动 single 线程,最终您可以使用 IO 使用多个 线程的调度程序。
另请注意,多个协程可以运行在单个线程上,但它们永远不会并行执行,它们可能显示为 运行ning 并行,因为当一个新协程 launched 或 suspended 时,线程从一个协程切换到另一个协程。