分块、异步和等待的 Kotlin 奇怪行为
Kotlin strange behaviour with chunked, async and await
我正在编写一些代码,涉及来自列表中元素的大量并行请求,我遇到了一个奇怪的行为:
import kotlinx.coroutines.*
suspend fun main() {
coroutineScope {
val hello = listOf("hello", "world")
val chunks = hello.chunked(1) {
async { translate(it.first()) }
}
chunks.forEach {
print(it.await())
}
}
}
private fun translate(word: String): String {
return if(word == "hello") {
"hola"
} else {
"mundo"
}
}
它应该显示“holamundo”,但有时这个例子会打印“mundomundo”。
我也在 Kotlin Playground 上提供了它。
我在这里缺少什么?
此行为的原因是 async
中的代码不是 运行 立即。它只是预定的。调用 lambda 之前的 async
块 returns。这就是为什么 async
块中的 it
总是指向 ["world"]
window 因此最终输出是“mundomundo”。
举个例子:
fun main() {
runBlocking {
var a = 1
val deferred = async {
println(a)
}
a++
deferred.await()
}
}
Playground
在此示例中,输出将是 2
而不是 1
,因为 a++
在 async
lambda 之前处理。
编辑: 正如@IR42 在评论中指出的那样,文档清楚地提到传递给转换函数的列表变化非常快,所以如果你想异步使用它你应该先复制一份。
val chunks = hello.chunked(1) {
val copy = it.toList()
async { translate(copy.first()) }
}
此代码将为您提供预期的输出。
另一种解决方案(感谢@broot)是先计算整个分块列表,然后将其映射到延迟列表。
val chunks = hello.chunked(1).map {
async { translate(it.first()) }
}
我正在编写一些代码,涉及来自列表中元素的大量并行请求,我遇到了一个奇怪的行为:
import kotlinx.coroutines.*
suspend fun main() {
coroutineScope {
val hello = listOf("hello", "world")
val chunks = hello.chunked(1) {
async { translate(it.first()) }
}
chunks.forEach {
print(it.await())
}
}
}
private fun translate(word: String): String {
return if(word == "hello") {
"hola"
} else {
"mundo"
}
}
它应该显示“holamundo”,但有时这个例子会打印“mundomundo”。
我也在 Kotlin Playground 上提供了它。
我在这里缺少什么?
此行为的原因是 async
中的代码不是 运行 立即。它只是预定的。调用 lambda 之前的 async
块 returns。这就是为什么 async
块中的 it
总是指向 ["world"]
window 因此最终输出是“mundomundo”。
举个例子:
fun main() {
runBlocking {
var a = 1
val deferred = async {
println(a)
}
a++
deferred.await()
}
}
Playground
在此示例中,输出将是 2
而不是 1
,因为 a++
在 async
lambda 之前处理。
编辑: 正如@IR42 在评论中指出的那样,文档清楚地提到传递给转换函数的列表变化非常快,所以如果你想异步使用它你应该先复制一份。
val chunks = hello.chunked(1) {
val copy = it.toList()
async { translate(copy.first()) }
}
此代码将为您提供预期的输出。
另一种解决方案(感谢@broot)是先计算整个分块列表,然后将其映射到延迟列表。
val chunks = hello.chunked(1).map {
async { translate(it.first()) }
}