Sobes.tech
Middle

¿Cómo configurar y usar corrutinas en proyectos de Kotlin?

sobes.tech AI

Respuesta de la IA

Para configurar las corrutinas, es necesario agregar la dependencia correspondiente en el archivo build.gradle.

// build.gradle (módulo app)
dependencies {
    //... otras dependencias
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Para JVM, Android, JS, Native
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // Para Android (si se necesita Dispatchers.Main)
}

El uso de corrutinas incluye:

  1. Ámbito de vida (Scope): Definir el ámbito en el que las corrutinas se ejecutarán y gestionar su ciclo de vida.

    • GlobalScope: Ámbito de toda la aplicación, usar con precaución, ya que puede causar fugas de memoria.
    • CoroutineScope: Se crea explícitamente, permite controlar la cancelación de las corrutinas.
    • Ámbitos predefinidos en frameworks (por ejemplo, lifecycleScope en Android ViewModel).
    // Ejemplo con CoroutineScope
    val myScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) // Crear Scope con Dispatcher y Job
    
    fun performBackgroundTask() {
        myScope.launch { // Iniciar corrutina
            // Proceso de cálculo largo
        }
    }
    
    fun cancelAllTasks() {
        myScope.cancel() // Cancelar todas las corrutinas en el Scope
    }
    
  2. Constructores de corrutinas (Builders): Funciones para lanzar corrutinas.

    • launch: Inicia una corrutina que no devuelve resultado explícito (devuelve Job).
    • async: Inicia una corrutina que devuelve resultado en forma de Deferred.
    import kotlinx.coroutines.*
    
    suspend fun fetchData() = coroutineScope { // coroutineScope - constructor para concurrencia estructurada
        val data1 = async { networkCall1() } // async devuelve Deferred
        val data2 = async { networkCall2() }
    
        // ... procesamiento de datos después de obtenerlos
        data1.await() // Obtener resultado de Deferred, suspende la corrutina hasta completar
        data2.await()
    }
    
    suspend fun networkCall1(): String {
        delay(1000) // Simulación de solicitud de red
        return "Data 1"
    }
    
    suspend fun networkCall2(): String {
        delay(1500)
        return "Data 2"
    }
    
    fun startFetching() = runBlocking { // runBlocking - constructor para bloquear el hilo actual (para main o pruebas)
        launch { // launch devuelve Job
            println("Fetching data...")
            fetchData()
            println("Data fetched!")
        }
    }
    
  3. Despachadores (Dispatchers): Determinan en qué hilo o grupo de hilos se ejecutará la corrutina.

    • Dispatchers.Default: Para tareas intensivas en CPU.
    • Dispatchers.IO: Para operaciones de E/S bloqueantes (archivos, red).
    • Dispatchers.Main: Hilo principal (solo para Android y Swing/JavaFX).
    • Dispatchers.Unconfined: Ejecuta la corrutina en el hilo que la llamó, se suspende y luego se reanuda en el hilo que la reanudó.
    import kotlinx.coroutines.*
    
    fun simpleTask() {
        GlobalScope.launch(Dispatchers.IO) { // Especificar despachador
            // Ejecutar operación de E/S
            println("Ejecutándose en el hilo: ${Thread.currentThread().name}")
        }
    }
    
    fun main() = runBlocking {
        launch(Dispatchers.Default) {
            println("Hilo por defecto: ${Thread.currentThread().name}")
        }
        launch(Dispatchers.Unconfined) {
             println("Hilo no confinado 1: ${Thread.currentThread().name}")
             delay(100) // Suspensión
             println("Hilo no confinado 2: ${Thread.currentThread().name}") // Reanudación en otro hilo (posiblemente)
        }
        delay(200) // Esperar a que terminen las corrutinas
    }
    
  4. Funciones de suspensión (Suspending functions): Funciones marcadas con la palabra clave suspend, que pueden suspenderse y reanudarse sin bloquear el hilo. Solo pueden llamarse desde otras funciones de suspensión o bloques de corrutinas.

  5. Manejo de cancelación y excepciones: Las corrutinas soportan cancelación estructurada y propagación de excepciones.

    • Cancelación:
      import kotlinx.coroutines.*
      
      fun cancellableTask() = GlobalScope.launch {
          try {
              repeat(1000) { i ->
                  println("Trabajando $i ...")
                  delay(100) // función de suspensión que respeta la cancelación
              }
          } catch (e: CancellationException) {
              println("¡Tarea cancelada!")
          } finally {
              println("Limpieza...")
          }
      }
      
      fun main() = runBlocking {
          val job = cancellableTask()
          delay(500) // Esperar un poco
          job.cancelAndJoin() // Cancelar y esperar a que termine
      }
      
    • Excepciones: En launch, una excepción no capturada causará la caída de la aplicación (en Android, por defecto). En async, la excepción se lanzará al llamar a await(). Para control más granular, se usan CoroutineExceptionHandler o SupervisorJob.
    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val handler = CoroutineExceptionHandler { _, exception ->
            println("Excepción capturada: $exception")
        }
    
        val job = GlobalScope.launch(handler) { // Aplicar manejador
            throw IllegalStateException("Algo salió mal")
        }
        job.join() // Esperar a que termine
    }