50 - Corrutinas: launch y runBlocking

Una corrutina es conceptualmente similar a un hilo (thread) que se implementan en otros lenguajes o inclusive en Kotlin ya que podemos acceder a la clase Thread de Java. Las corrutinas se pueden considerar como subprocesos livianos.

Vimos en el concepto anterior que si el hilo principal de nuestro programa finaliza luego todas las corrutinas en ejecución también finalizan, ahora veremos que mediante la llamada a la función runBlocking podemos bloquear nuestro hilo principal de la aplicación hasta que todas las corrutinas finalicen.

Creemos un proyecto, agreguemos como siempre las dependencias de la biblioteca de corrutinas:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")

Implementamos el siguiente código:

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking {
    launch {
        delay(1000)
        println("Paso un segundo")
    }
    println("Iniciando")
}

Cuando ejecutamos tenemos como resultado:

Coroutines runBlocking launch

runBlocking es también un constructor de corrutinas que une el mundo que no es de corrutina de una función regular fun main() y el código con corrutinas dentro de runBlocking.

Podemos ver que no hemos dispuesto una llamada a readLine, por un lado debido a que ahora la función main es de una única expresión que se le asigna la función runBlocking.

Hasta que no finalizan por completo todas las corrutinas que tienen dentro de runBlocking (una en este caso que llamamos mediante la función launch), no finaliza la función main.

launch es un constructor de corrutinas. Lanza una nueva corrutina al mismo tiempo que el resto del código, que continúa funcionando de forma independiente. Por eso aparece primero "Iniciando" y luego de un segundo se muestra "Paso un segundo".

El nombre de runBlocking significa que el hilo que lo ejecuta (en este caso, el hilo principal de nuestro programa) se bloquea durante la duración de la llamada, hasta que todas las corrutinas internas de runBlocking { ... } completen su ejecución.

Refactorización de funciones.

Cuando queremos mover un algoritmo que contiene una corrutina a otra función, a la misma hay que agregarle el modificador 'suspend'. Veamos los cambios que hay que hacer con el ejemplo anterior:

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking {
    launch {
        espera()
    }
    println("Iniciando")
}

suspend fun espera() {
    delay(1000)
    println("Paso un segundo")
}

Las funciones de suspensión se pueden usar dentro de las corrutinas al igual que las funciones normales, pero su característica adicional es que pueden, a su vez, usar otras funciones de suspensión (como delay en este ejemplo) para suspender la ejecución de una corrutina.

La corrutinas son livianas.

A diferencia de los hilos (Thread) las corrutinas requieren muy pocos recursos para su creación y mantenimiento en su ejecución, podemos probar de crear 100000 corrutinas con el siguiente código:

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking {
    for(x in 1..100000)
        launch {
            delay(1000)
            print(".")
        }
}

Podemos observar que no hay problemas de performance en su ejecución, a pesar de hacer creado 100000 corrutinas. Si intentamos hacer lo mismo creando 100000 hilos (Thread) podremos comprobar que se genera un error en la aplicación.

Manejador que retorna launch

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main(args: Array<String>) = runBlocking {
    val corrutina1=launch {
        delay(1000)
        println("Paso un segundo")
    }
    corrutina1.join()
    val corrutina2=launch {
        delay(1000)
        println("Paso otro segundo")
    }
    corrutina2.join()
    println("Finalizado")
}

La ejecución del programa muestra:

Paso un segundo
Paso otro segundo
Finalizado

La función launch retorna un objeto de tipo Job que es un identificador de la corrutina iniciada y se puede usar para esperar explícitamente a que se complete. Dicha acción se hace llamando al método join.