Kotlin 奇怪的分块、异步和等待行为

标签 kotlin asynchronous kotlin-coroutines

我正在编写一些代码,涉及对列表中的元素进行批量并行请求,但我遇到了奇怪的行为:

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/

相关文章:

android - android项目中需要匹配kotlin文件的package语句和目录吗?

java - 如何按原样发送字节数,而不是将它们作为 Android 蓝牙输出流中的单独数字发送?

arrays - Nodejs中通过for循环构造数组

python - 如何永远运行异步 websocket 客户端?

android - 使用 Dispatchers.IO 进行 Kotlin 协程测试

android - 使用来自 kotlinx-coroutine-test 的类时出错

android - 使用 google mlkit 视觉样本减少跟踪窗口

kotlin - 如何替换 Kotlin 中读取字节的阻塞代码

javascript - 访问异步函数返回的最佳方式

kotlin-coroutines - Kotlin Coroutines对IO线程的利用