我正在编写一些代码,涉及对列表中的元素进行批量并行请求,但我遇到了奇怪的行为:
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
里面的代码不是立即运行。这只是预定的。 async
block 在调用 lambda 之前返回。这就是为什么 it
里面async
block 始终指向 ["world"]
窗口,因此最终输出是“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)是首先计算整个分块列表,然后将其映射到 deferred 列表。
val chunks = hello.chunked(1).map {
async { translate(it.first()) }
}
关于Kotlin 奇怪的分块、异步和等待行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70187761/