O que e Job em Kotlin?
O Job e o elemento do contexto de uma coroutine que representa seu ciclo de vida. Ele permite controlar a execucao da coroutine: verificar se esta ativa, esperar sua conclusao ou cancela-la. Todo launch retorna um Job, e todo async retorna um Deferred (que e um subtipo de Job com resultado).
Pense no Job como a “alca” que voce segura para controlar uma coroutine que esta rodando em background.
Sintaxe basica
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
delay(2000)
println("Coroutine finalizada")
}
println("Job ativo? ${job.isActive}") // true
println("Job completo? ${job.isCompleted}") // false
job.join() // Espera a coroutine terminar
println("Job ativo? ${job.isActive}") // false
println("Job completo? ${job.isCompleted}") // true
}
O metodo join() suspende a coroutine atual ate que o job termine. Diferente de Thread.join(), ele nao bloqueia a thread.
Estados do Job
Um Job passa por varios estados durante sua vida:
New -> Active -> Completing -> Completed
|
v
Cancelling -> Cancelled
fun main() = runBlocking {
val job = launch(start = CoroutineStart.LAZY) {
println("Executando...")
delay(1000)
println("Finalizado")
}
println("Novo: isActive=${job.isActive}") // false
job.start()
println("Ativo: isActive=${job.isActive}") // true
job.join()
println("Completo: isCompleted=${job.isCompleted}") // true
}
Com CoroutineStart.LAZY, o job comeca no estado “New” e so entra em “Active” quando voce chama start() ou join().
Cancelamento de Jobs
O cancelamento e cooperativo em Kotlin. Chamar cancel() nao mata a coroutine instantaneamente – ela precisa verificar o cancelamento:
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("Trabalhando... $i")
delay(100) // Ponto de suspensao: verifica cancelamento
}
}
delay(500)
println("Cancelando...")
job.cancel()
job.join() // Espera a coroutine finalizar o cancelamento
// Ou: job.cancelAndJoin() // Combina cancel() + join()
println("Cancelado")
}
Funcoes como delay, yield e withContext verificam o cancelamento automaticamente. Se sua coroutine faz trabalho intensivo de CPU sem pontos de suspensao, use isActive ou ensureActive():
val job = launch(Dispatchers.Default) {
var i = 0
while (isActive) { // Verifica cancelamento manualmente
i++
// Trabalho intensivo de CPU
}
println("Parou em $i")
}
Hierarquia de Jobs (Structured Concurrency)
Jobs formam uma hierarquia pai-filho. Quando uma coroutine lanca outra, o job filho e registrado no job pai:
fun main() = runBlocking {
val jobPai = launch {
val jobFilho1 = launch {
delay(2000)
println("Filho 1 finalizado")
}
val jobFilho2 = launch {
delay(1000)
println("Filho 2 finalizado")
}
println("Filhos lancados")
}
delay(500)
jobPai.cancel() // Cancela pai E todos os filhos
jobPai.join()
println("Tudo cancelado")
}
Regras da hierarquia:
- Cancelar o pai cancela todos os filhos.
- Uma excecao nao tratada em um filho cancela o pai e todos os irmaos.
- O pai so e considerado completo quando todos os filhos terminam.
Job vs Deferred
Deferred<T> e um Job que retorna um resultado:
fun main() = runBlocking {
val deferred: Deferred<Int> = async {
delay(1000)
42
}
// Deferred tem todas as funcionalidades de Job
println("Ativo: ${deferred.isActive}")
// Mais: pode obter o resultado
val resultado = deferred.await()
println("Resultado: $resultado")
}
Use launch (Job) quando nao precisa de resultado; use async (Deferred) quando precisa.
SupervisorJob
O SupervisorJob modifica o comportamento de propagacao de erros: a falha de um filho nao cancela os irmaos:
fun main() = runBlocking {
val supervisor = SupervisorJob()
val scope = CoroutineScope(coroutineContext + supervisor)
val job1 = scope.launch {
delay(1000)
throw RuntimeException("Erro no job 1")
}
val job2 = scope.launch {
delay(2000)
println("Job 2 finalizado normalmente") // Executa mesmo com erro no job1
}
delay(3000)
supervisor.cancel()
}
SupervisorJob e essencial em cenarios onde tarefas sao independentes e a falha de uma nao deve afetar as outras.
Completable Job
CompletableJob e um Job que pode ser completado manualmente:
fun main() = runBlocking {
val job = Job() // Cria um CompletableJob
launch(job) {
delay(1000)
println("Tarefa executada")
}
job.complete() // Marca como completando (nao aceita mais filhos)
job.join() // Espera filhos existentes terminarem
println("Tudo feito")
}
Quando usar Job diretamente
- Cancelamento controlado: quando voce precisa cancelar uma operacao especifica em resposta a uma acao do usuario.
- Esperar conclusao: quando uma parte do codigo depende de outra coroutine terminar antes de continuar.
- Monitorar estado: quando voce precisa saber se uma coroutine ainda esta rodando.
- Escopo customizado: criar CoroutineScopes com Jobs especificos para controlar grupos de coroutines.
Erros comuns
Nao chamar join apos cancel:
cancel()apenas sinaliza o cancelamento. Semjoin(), a coroutine pode continuar executando por um tempo. UsecancelAndJoin().Ignorar a cooperatividade do cancelamento: loops de CPU sem verificacao de
isActiveou pontos de suspensao nao serao cancelados.Usar Job() como pai sem entender a propagacao: um Job criado manualmente com
Job()segue as regras normais de propagacao de erro. Para isolamento, useSupervisorJob().Esquecer o tratamento de CancellationException: quando um job e cancelado,
CancellationExceptione lancada. Ela deve ser propagada, nao capturada emcatchgenerico.Nao estruturar coroutines: lancar coroutines com
GlobalScopeem vez de usar escopos estruturados perde todo o beneficio de hierarquia de Jobs.
Termos relacionados
- Coroutine: a unidade de execucao cujo ciclo de vida o Job representa.
- Deferred: subtipo de Job que carrega um resultado, retornado por
async. - SupervisorJob: job especial onde falha de filhos nao propaga para irmaos.
- CoroutineScope: escopo que contem um Job e define o ciclo de vida das coroutines.
- Dispatcher: trabalha junto com o Job no contexto da coroutine, determinando onde ela executa.
- Structured Concurrency: o principio de que coroutines formam hierarquias atraves de Jobs.
O Job e a peca central do controle de ciclo de vida em Kotlin Coroutines. Dominar Jobs, cancelamento e hierarquia e fundamental para escrever codigo concorrente que seja robusto, previsivel e livre de vazamentos de recursos.