49 - Corrutinas

La programación asíncrona o sin bloqueo es resuelta en el lenguaje Kotlin mediante una tecnología llamada Coroutines que viene implementada una parte en el lenguaje Kotlin propiamente dicho y otra gran parte mediante una biblioteca de funciones.

La programación asíncrona es muy conveniente cuando tenemos que desarrollar algoritmos que requieren muchos recursos, consultas a servidores de internet, consultas a bases de datos, descarga de archivos grandes etc. con el objetivo de no bloquear el hilo principal de nuestra aplicación y que el usuario se vea impedido de interactuar con el programa hasta que termine de ejecutar el algoritmo.

Además de lograr el objetivo que la aplicación no se bloquee hasta terminar el proceso, también son indispensables las corutinas para implementar aplicaciones escalables, podemos tener programas mucho más escalables ejecutando distintas rutinas en forma simultánea en distintos procesadores.

Cuando queremos trabajar con corutinas en Kotlin debemos importar la biblioteca kotlinx.coroutines, podemos visitar el sitio donde se publican las últimas actualizaciones en el sitio oficial.

Problema 1

Importar la biblioteca para trabajar con corutinas en un proyecto de Kotlin. Implementar una aplicación mínima que muestre los números de 1 al 10 mostrándose de uno en uno cada 1 segundo, resolverlo dentro de una corrutina.

Crearemos un proyecto de consola con IntelliJ IDEA:

Coroutines

Agregamos las dependencias de la biblioteca de corrutinas en el archivo build.gradle.kts:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
Coroutines build.gradle.kts

Ahora si podemos ir al archivo Main.kt y codificar la aplicación:

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

fun main(args: Array<String>) {
    GlobalScope.launch {
        for(x in 1..10) {
            print("$x -")
            delay(1000)
        }
    }
    println("Se bloquea el hilo principal del programa al llamar a readLine")
    readLine()
}

En pantalla tenemos como resultado:

Coroutines

Como podemos comprobar lo primero que aparece en pantalla es el mensaje que muestra la llamada a println:

    println("Se bloquea el hilo principal del programa al llamar a readLine")

Seguidamente bloqueamos el hilo principal de nuestro programa llamando a la función readLine (si el programa finaliza, todas las corrutinas que se hay iniciado finalizan en forma automática):

    readLine()

Luego podemos comprobar que comienzan a aparecer en pantalla los números del 1 al 10 de uno en uno, lentamente.

La creación de una corrutina se logra llamando a la función 'launch' y pasando una función lambda con el algoritmo que queremos que se ejecute en forma paralela al hilo principal de nuestro programa:

    GlobalScope.launch {
        for(x in 1..10) {
            print("$x -")
            delay(1000)
        }
    }

Recordemos que en Kotlin podemos indicar directamente la implementación de la función, en lugar de disponer el nombre del parámetro, la signación y la implementación de la función:

    GlobalScope.launch(block = {
        for (x in 1..10) {
            print("$x -")
            delay(1000)
        }
    })

Dentro de la función lambda disponemos un for que se repetirá 10 veces y en su interior mostramos el contador y detenemos la ejecución de la corrutina mediante la llamada a la función delay pasando la cantidad de milisegundos a detenerse.

Problema 2

Lanzar 2 corrutinas, en la primera mostrar los números del 1 al 10 y en la segundo los números del 11 al 20.

Creamos el Proyecto192, procedemos a agregar la biblioteca que permite trabajar con corrutinas e implementamos el siguiente algoritmo:

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

fun main(args: Array<String>) {
    GlobalScope.launch {
        for(x in 1..10) {
            print("$x ")
            delay(1000)
        }
    }
    GlobalScope.launch {
        for(x in 11..20) {
            print("$x ")
            delay(1000)
        }
    }
    readLine()
}
Coroutines

Es muy importante notar que las dos corrutinas se ejecutan en forma simultánea, no se requiere terminar la primer corrutina donde se muestran los números del 1 al 10 para que comience la segundo corrutina donde se muestran los números del 11 al 20, como vemos aparecen intercalados los resultados de cada corrutina.

Problema propuesto

  • Confeccionar una aplicación que en su hilo principal se genere un valor aleatorio entre 1 y 100:

    fun main(args: Array) {
        val adivina = Random.nextInt(1, 100)
    

    Luego crear una corrutina donde la misma debe adivinar el número aleatorio generado en el hilo principal. Se debe generar un numero aleatorio, la primera vez entre 1 y 100, luego verificar si el número aleatorio a adivinar del hilo principal es mayor o menor. Si es igual mostrar que ganó y si no detenerse por 500 milisegundos y proceder a repetir la generación de otro número aleatorio pero ahora acotado a la respuesta de si era mayor o menor.
    Repetir el proceso hasta que gane.

    Coroutines
Solución
Proyecto193



import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.random.Random

fun main(args: Array<String>) {
    val adivina = Random.nextInt(1, 100)
    var inicio = 1
    var fin = 100
    GlobalScope.launch {
        var pienso:Int
        do {
            pienso = Random.nextInt(inicio, fin)
            println(pienso)
            if (pienso == adivina)
                println("Gane con el $pienso")
            else
                if (pienso < adivina) {
                    println("El numero es mayor")
                    inicio = pienso
                } else {
                    println("El numero es menor")
                    fin = pienso
                }
            delay(500)
        } while (pienso != adivina)
    }
    readLine() //detenemos el hilo principal del programa
}