Estoy haciendo un código que involucra una cantidad de solicitudes paralelas en lotes de elementos en una lista y me encuentro con un comportamiento extraño:
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" } }Debería mostrar "holamundo", pero a veces este ejemplo imprime "mundomundo" en su lugar.
También lo puse a disposición en Kotlin Playground .
¿Qué me estoy perdiendo aquí?
El motivo de este comportamiento es que el código dentro de async no se ejecuta inmediatamente. Solo está programado. El bloque async regresa antes de que se invoque la lambda. Esta es la razón por la it el bloque async interno siempre apunta a la ventana ["world"] y, por lo tanto, la salida final es "mundomundo".
Toma este ejemplo:
fun main() { runBlocking { var a = 1 val deferred = async { println(a) } a++ deferred.await() } } Patio de recreo
En este ejemplo, la salida será 2 y no 1 porque a++ se procesa antes que la lambda async .
Editar: como señaló @ IR42 en el comentario, la documentación menciona claramente que la lista pasada a la función de transformación cambia muy rápidamente, por lo que si desea usarla de forma asíncrona, primero debe hacer una copia de ella.
val chunks = hello.chunked(1) { val copy = it.toList() async { translate(copy.first()) } }Este código le dará el resultado esperado.
Otra solución (gracias a @broot) es calcular primero la lista fragmentada completa y luego asignarla a la lista de diferidos.
val chunks = hello.chunked(1).map { async { translate(it.first()) } }