Jonathan Briceño
12/20/2021, 9:26 PMJonathan Briceño
12/20/2021, 9:26 PMimport kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
fun main() = runBlocking {
val timeInMillis = measureTimeMillis {
paso1()
println("----------------------------------------------------------------------")
val prueba = prueba()
println("----------------------------------------------------------------------")
println("size ${prueba.lista.size}")
println("field 2 ${prueba.mapa["2"]}")
paso3(prueba.lista)
}
println("la operacion duro $timeInMillis ms")
}
suspend fun prueba(): Objeto= coroutineScope {
val lista = listOf("campo1", "campo2", "campo3", "campo4", "campo5")
var mapa: HashMap<String, String> = hashMapOf()
var listaFinal: MutableList<String> = mutableListOf()
var i = 0
launch {
lista.forEach {
i++
mapa[i.toString()] = it
makeObject(it).let { camp ->
listaFinal.add(camp)
}
}
}
println("end")
return@coroutineScope Objeto(mapa = mapa, lista= listaFinal)
}
fun makeObject(i: String): String {
return "hola $i"
}
fun paso1() {
println("hice el paso 1")
}
fun paso3(lista: MutableList<String>) {
lista.forEach {
println("Resultado $it")
}
}
data class Objeto(
val mapa: HashMap<String, String>,
val lista: MutableList<String>
)
Jonathan Briceño
12/20/2021, 9:27 PMimport kotlinx.coroutines.*
fun main() = runBlocking {
var result = doWorld()
println("Hi friends")
println(result.a)
println(result.b)
}
suspend fun doWorld(): Result = coroutineScope {
var res: Result
var aRes = 0
var bRes = 0
val a = listOf(1, 2, 3, 4, 5)
launch {
a.forEach {
println("Hola malditos")
}
}
println("$aRes ---- $bRes")
return@coroutineScope Result(a = aRes, b = bRes)
}
data class Result(
var a: Int,
val b: Int
)
Jonathan Briceño
12/20/2021, 9:27 PMCasey Brooks
12/20/2021, 10:08 PMJonathan Briceño
12/20/2021, 10:10 PMJoffrey
12/20/2021, 10:30 PMcoroutineScope
block does wait for the launch to complete before returning (in both cases), but the returned value is the one that was constructed earlier.
So why do you see apparently different results? The devil is in the details. In the first case, the `Objeto`'s properties are references to objects (a map and a list), so even if the Objeto
instance is created before anything in the launch executes, it's still the same map and list instances that are returned in the end. In the meantime, the launch
can fill them in and you can see all the values in the Objeto
in the end.
In the second case, the properties of Result
are both of primitive types (Int
). So their value is "copied" from aRes
and bRes
when the Result
intance is constructed. Changing aRes
and bRes
later during the `launch`'s execution doesn't have any effect on the properties of the Result
instance that you created.Casey Brooks
12/20/2021, 10:30 PMend
immediately, and it's only later that prueba()
actually returns and gives you the results you're expecting. Technically, Objeto()
is being created and holding references to mutable collections, and those collections are being updated in the background, and it just so happens that the whole prueba()
function behaves nicely and things look like they're working like you'd expect. But it's actually not doing what you think, it's just giving you the same results anyway.
That's why the second example seems like it doesn't work; it's not the code itself that is wrong, it's your intuition of how coroutines work that is wrong (which is totally understandable, you've got multiple complex systems interacting in subtle and unintuitive ways here!)Casey Brooks
12/20/2021, 10:32 PMObjeto
holding references to mutable collections, which could be updated at any time by any thread, and it would never know. That is how race conditions are created, and to check this is true, try returning Objeto(mapa = mapa.toMap(), lista= listaFinal.toList())
instead; that will break the reference to the mutable object, and you'll see that when Objeto
is created, those collections are still empty, and they will remain empty after prueba()
returnsCasey Brooks
12/20/2021, 10:35 PMlaunch { }
blocks have completed before creating Objeto
. To do that, you'd need to wrap just that one portion of the suspend function in coroutineScope { }
.Casey Brooks
12/20/2021, 10:36 PMsuspend fun prueba(): Objeto {
val lista = listOf("campo1", "campo2", "campo3", "campo4", "campo5")
var mapa: HashMap<String, String> = hashMapOf()
var listaFinal: MutableList<String> = mutableListOf()
var i = 0
coroutineScope {
launch {
lista.forEach {
i++
mapa[i.toString()] = it
makeObject(it).let { camp ->
listaFinal.add(camp)
}
}
}
}
println("end")
return Objeto(mapa = mapa.toMap(), lista= listaFinal.toList())
}
Joffrey
12/20/2021, 10:37 PMcoroutineScope { launch { doStuff() } }
is pointless though, because it's equivalent to just doStuff()
.
I think there is also a misconception here about the point of using launch
. This won't magically parallelize your operations inside the launched block, a single launch
is still like a single thread. If you want multiple concurrent things to happen, you need multiple launch
(or if you stick to one launch
, you need at least something that runs concurrently with the one launch
to make it useful, which is not the case when there is nothing else in the coroutineScope
)Jonathan Briceño
12/20/2021, 10:43 PMJoffrey
12/20/2021, 10:46 PM