Sobes.tech
Middle

¿Qué sucede con una coroutine cuando ocurre una excepción?

sobes.tech AI

Respuesta de la IA

Cuando ocurre una excepción dentro de una coroutine, la dependencia del tipo de excepción (y la configuración del dispatcher/contexto) determina cómo será manejada.

  • Excepción no controlada: Si la excepción no es capturada dentro de la coroutine, se propaga hacia arriba en la jerarquía de coroutines hasta el Job o CoroutineScope más cercano. El comportamiento depende del tipo de Job:

    • Job: Por defecto, cuando ocurre una excepción en una coroutine hija, el Job padre se cancela, lo que lleva a la cancelación de todas sus demás coroutines hijas. La excepción se propaga hacia arriba.
    • SupervisorJob: El SupervisorJob padre no se cancela cuando ocurre una excepción en una coroutine hija. Solo la coroutine en la que ocurrió la error se cancela. Esto es útil cuando quieres que la cancelación de una tarea hija no afecte a las otras.
    • CoroutineScope raíz: Si la excepción alcanza el CoroutineScope raíz (por ejemplo, GlobalScope o un scope creado con CoroutineScope sin Job), se pasa al manejador global de excepciones CoroutineExceptionHandler. Por defecto, si no hay un manejador registrado, la excepción se registra en System.err.
  • Excepción controlada: Si la excepción es capturada con un bloque try-catch dentro de la coroutine, se maneja localmente y la coroutine continúa ejecutándose después del bloque catch, si es posible. La excepción no se propaga hacia arriba.

  • Excepciones relacionadas con la cancelación (por ejemplo, CancellationException): Estas excepciones son especiales. Se usan para el mecanismo de cancelación de coroutines y generalmente son ignoradas por la mayoría de los manejadores de excepciones por defecto. No causan una falla fatale en el Job padre por defecto.

  • Excepciones en async: La coroutine lanzada con async coloca la excepción en su Deferred. La excepción solo se propaga cuando se llama a .await() en ese Deferred. Hasta ese momento, la excepción está "oculta".

import kotlinx.coroutines.*

fun main() = runBlocking {
    val scope = CoroutineScope(SupervisorJob())

    scope.launch {
        try {
            println("Coroutine 1 started")
            delay(100)
            throw RuntimeException("Something went wrong in Coroutine 1")
        } catch (e: Exception) {
            println("Coroutine 1 caught exception: ${e.message}")
        }
    }

    scope.launch {
        println("Coroutine 2 started")
        delay(500)
        println("Coroutine 2 finished")
    }

    delay(1000) // Esperamos para ver la salida
    scope.cancel() // Cancelamos el scope
}

En este ejemplo, la excepción en la Coroutine 1 se captura, y la Coroutine 2 continúa ejecutándose porque se usa SupervisorJob. Si en lugar de SupervisorJob se usara un Job normal, la excepción en la Coroutine 1 provocaría la cancelación de la Coroutine 2.