DEV Community

Isaias Velasquez
Isaias Velasquez

Posted on

How to dominar escopos e excecoes em coroutines

Depois que voce aprende o basico de coroutines, as perguntas que ficam sao sobre escopos e tratamento de excecoes. Vou mostrar como lidar com esses dois na pratica.

Comece importando o essencial:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
Enter fullscreen mode Exit fullscreen mode

O escopo define onde a coroutine vive e quando ela morre. O erro mais comum e lancar uma coroutine sem escopo, e ela continuar rodando depois que a tela fechou ou o servico terminou.

Exemplo com GlobalScope (nao faca isso):

GlobalScope.launch {
    delay(5000)
    println("Ainda rodando...")
}
Enter fullscreen mode Exit fullscreen mode

Essa coroutine viveria para sempre. Use escopos com ciclo de vida definido.

No Android, use o viewModelScope:

class MeuViewModel : ViewModel() {
    fun carregarDados() {
        viewModelScope.launch {
            val dados = api.buscarDados()
            // atualiza a UI
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Quando o ViewModel for limpo, todas as coroutines dentro dele sao canceladas automaticamente.

Em aplicacoes Spring, crie seu proprio escopo com CoroutineScope:

@Service
class MeuServico {
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    fun processar() {
        scope.launch {
            delay(1000)
            println("processamento feito")
        }
    }

    @PreDestroy
    fun cleanup() {
        scope.cancel()
    }
}
Enter fullscreen mode Exit fullscreen mode

O SupervisorJob e importante: ele faz com que a falha de uma coroutine filha nao cancele as outras. Veja a diferenca:

val job = SupervisorJob()
val scope = CoroutineScope(job + Dispatchers.IO)

scope.launch {
    delay(100)
    throw RuntimeException("falha na 1")
}
scope.launch {
    delay(200)
    println("essa ainda executa")
}
Enter fullscreen mode Exit fullscreen mode

Com Job normal, a segunda coroutine seria cancelada quando a primeira falhasse. Com SupervisorJob, ela continua.

Para tratamento de excecoes, use try/catch dentro da coroutine:

scope.launch {
    try {
        val resultado = api.chamadaPerigosa()
        println(resultado)
    } catch (e: Exception) {
        println("erro na chamada: ${e.message}")
    }
}
Enter fullscreen mode Exit fullscreen mode

Ou use um CoroutineExceptionHandler global:

val handler = CoroutineExceptionHandler { _, exception ->
    println("nao tratado: ${exception.message}")
}

scope.launch(handler) {
    throw RuntimeException("algo deu errado")
}
Enter fullscreen mode Exit fullscreen mode

O handler captura excecoes que nao foram tratadas dentro da coroutine. Mas ele nao funciona com async:

scope.async {
    throw RuntimeException("isso nao vai pro handler")
}
Enter fullscreen mode Exit fullscreen mode

No async, a excecao so aparece quando voce chama await():

scope.launch {
    val deferred = scope.async {
        throw RuntimeException("erro no async")
        "resultado"
    }
    try {
        deferred.await()
    } catch (e: Exception) {
        println("capturado no await: ${e.message}")
    }
}
Enter fullscreen mode Exit fullscreen mode

Para escopos em camadas, use withContext para trocar de dispatcher:

scope.launch {
    val dados = withContext(Dispatchers.IO) {
        // operacao bloqueante aqui
        api.buscarDoBanco()
    }
    // volta para o dispatcher principal
    println(dados)
}
Enter fullscreen mode Exit fullscreen mode

Escolha o dispatcher certo: Dispatchers.Main para UI, Dispatchers.IO para rede e banco, Dispatchers.Default para processamento pesado.

That's all for now.
Thanks for reading!

Top comments (0)