Sobes.tech
Middle

O que acontece com uma coroutine quando ocorre uma exceção?

sobes.tech IA

Resposta da IA

Quando ocorre uma exceção dentro de uma coroutine, a dependência do tipo de exceção (e as configurações do dispatcher/contexto) determina como ela será tratada.

  • Exceção não tratada: Se a exceção não for capturada dentro da coroutine, ela propaga-se para cima na hierarquia de coroutines até ao Job ou CoroutineScope mais próximo. O comportamento depende do tipo de Job:

    • Job: Por padrão, quando ocorre uma exceção numa coroutine filha, o Job pai é cancelado, levando ao cancelamento de todas as suas outras coroutines filhas. A exceção é propagada para cima.
    • SupervisorJob: O SupervisorJob pai não é cancelado quando ocorre uma exceção numa coroutine filha. Apenas a coroutine onde ocorreu a erro é cancelada. Isto é útil quando queres que o cancelamento de uma tarefa filha não afete as outras.
    • CoroutineScope raiz: Se a exceção atingir o CoroutineScope raiz (por exemplo, GlobalScope ou um scope criado com CoroutineScope sem Job), ela é passada ao gestor global de exceções CoroutineExceptionHandler. Por padrão, se não houver um gestor registado, a exceção é registada em System.err.
  • Exceção controlada: Se a exceção for capturada com um bloco try-catch dentro da coroutine, ela é tratada localmente, e a coroutine continua a sua execução após o bloco catch, se possível. A exceção não se propaga para cima.

  • Exceções relacionadas com cancelamento (por exemplo, CancellationException): Estas exceções são especiais. São usadas para o mecanismo de cancelamento de coroutines e geralmente são ignoradas pela maioria dos gestores de exceções por padrão. Elas não causam uma falha fatal no Job pai por padrão.

  • Exceções em async: A coroutine lançada com async coloca a exceção no seu Deferred. A exceção só se propaga quando se chama .await() nesse Deferred. Até lá, a exceção está "oculta".

import kotlinx.coroutines.*

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

    scope.launch {
        try {
            println("Coroutine 1 iniciada")
            delay(100)
            throw RuntimeException("Algo correu mal na Coroutine 1")
        } catch (e: Exception) {
            println("Coroutine 1 capturou exceção: ${e.message}")
        }
    }

    scope.launch {
        println("Coroutine 2 iniciada")
        delay(500)
        println("Coroutine 2 terminada")
    }

    delay(1000) // Espera para ver a saída
    scope.cancel() // Cancela o scope
}

Neste exemplo, a exceção na Coroutine 1 é capturada, e a Coroutine 2 continua a execução porque é usado SupervisorJob. Se fosse usado um Job normal, a exceção na Coroutine 1 levaria ao cancelamento da Coroutine 2.