分块、异步和等待的 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()) }
}