Kotlinautas
Esse conteúdo é oferecido e distribuído pela comunidade Kotlinautas, uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.
O quê são corotinas?
Corotinas (ou Coroutines) são um bloco de código que rodam concorrentemente com o resto do código, isso significa que podemos rodar dois blocos de código ao mesmo tempo, podendo assim ao mesmo tempo ler quanto enviar para um servidor por exemplo. Vamos ver mais sobre corotinas na prática durante o artigo.
Materiais
Será necessário ter o IntelliJ instalado na máquina e um conhecimento básico sobre a linguagem Kotlin.
Criando um projeto com Corotinas
Abra seu IntelliJ no menu inicial e clique em New Project:
Depois, selecione a opção Kotlin DSL build script, selecione também a opção Kotlin/JVM, e opicionalmente remova a primeira opção Java. Essa opção não vai mudar em nada, pois ela dá suporte do Gradle á linguagem Java, mas apenas iremos usar Kotlin.
Após isso, clique em Next e escreva o nome do projeto e a localização na sua máquina. Essas duas opção são completamente pessoais, caso não tenha nenhuma ideia, coloque algo como Corotinas apenas como identificação.
Agora, com o projeto aberto, vá ao aquivo build.gradle.kts e adicione a dependência implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"), com a seção dependencies ficando assim:
dependencies {
    implementation(kotlin("stdlib"))
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
}
Agora, clique no elefante no canto superior direito para carregar as alterações no Gradle.
Após isso, poderemos começar a programar. Você pode criar um arquivo em src/main/kotlin/ chamado main.kt para ser o arquivo principal da aplicação.
Mas com qualquer nome de arquivo, como você irá usar as corotinas, sempre se lembre de importar a biblioteca de corotinas no começo do arquivo:
import kotlinx.coroutines.*
Primeira Corotina
Vamos criar o primeiro exemplo, vamos criar uma corotina que irá rodar paralelamente com o código principal, o código principal apenas irá mostrar um "Olá", enquanto o código da corotina irá esperar um segundo, e após isso, irá mostrar um "Mundo!". Podemos fazer isso da seguinte forma:
import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        delay(1000L)
        println("Mundo!")
    }
    println("Olá")
}
Coloque esse código no seu IntelliJ e rode. O output esperado desse código é esse:
Olá
Mundo!
Agora vamos explicar o quê esse código está fazendo:
- 
RunBlockingé um bloco que irá armazenar todas as corotinas de uma parte do código, como se criasse um contexto diferente do normal damain. Todas as corotinas devem estar dentro de um blocorunBlocking;
- 
launchirá iniciar uma corotina, que irá funcionar concorrentemente (ao mesmo tempo) e independente do resto do código, podemos inserir quantos blocoslaunchque quisermos dentro de um mesmo código;
- 
delayé uma função que faz a corotina esperar por um tempo em milisegundos, e voltar com o processamento após esse tempo. Essa função recebe um número do tipoLong, que pode ser criado colocando umLno final de um número;
O runBlocking guarda um launch dentro, iniciando uma nova corotina, que a primeira instrução é o delay(1000L), fazendo que a corotina espere por um segundo (1000 milisegundos), enquanto isso o código principal continua, mandando um Olá na tela. E após um segundo da corotina rodando, a proxima e ultima instrução manda um Mundo! na tela.
Refatorando para uma função
Agora vamos transformar o conteúdo de dentro do bloco launch em uma função. Para isso, iremos precisar usar um suspend antes da função. (Função de suspensão)
Mas, o quê é esse
suspend?
funções com suspend são funções que podem ser usadas normalmente dentro de corotinas, mas podem usar algumas funções especiais, como a função delay que como foi explicado mais cedo, serve para fazer a corotina esperar um tempo em milisegundos.
Com isso em mente, vamos criar a função:
suspend fun escreverMundo() {
    delay(1000L)
    println("Mundo!")
}
E agora na main, vamos tirar tudo de dentro do bloco launch e rodar a função escreverMundo() dentro:
fun main() = runBlocking {
    launch { escreverMundo() }
    println("Olá")
}
Pronto! Agora nosso código está mais organizado, diminuindo o código da função main.
Escopo de Corotinas
Podemos também criar um escopo onde iremos armazenar corotinas dentro. Esse escopo se chama coroutineScope. Esse bloco é muito parecido com o bloco runBlocking, mas tem uma diferença, enquanto o runBlocking bloqueia a thread em uso enquanto está esperando algo, o coroutineScope libera a thread para outros usos enquanto espera algo.
Como o
coroutineScopeconsegue fazer isso?
Porque o coroutineScope é uma função de suspensão, enquanto o runBlocking é uma função normal. Por isso coroutineScope tem essas habilidades especiais.
Agora, vamos mudar a função escreverMundo, para fazer que essa função use os poderes de um coroutineScope:
suspend fun escreverMundo() = coroutineScope {
    launch {
        delay(1000L)
        println("Mundo!")
    }
    launch{
        delay(4000L)
        println("Já se passaram 4 segundos né?")
    }
    println("Olá")
}
- Agora, a função escreverMundorecebe umacoroutineScope;
- Como uma coroutineScope, podemos colocar vários blocoslaunchdentro. No caso, há dois blocos;
- O primeiro bloco, espera por um segundo e depois escreve um Mundo!na tela;
- O segundo bloco espera por 4 segundos, e depois escreve na tela Já se passaram 4 segundos né?;
- E abaixo destes dois blocos, há a instrução para escrever um Olána tela.
Por conta que essas três partes serão executadas ao mesmo tempo, primeiro irá aparecer Olá, depois de um segundo Mundo!, e depois de 4 segundos que o programa começou a rodar, irá aparecer o Já se passaram 4 segundos né?.
Mas para que esse código rode corretamente, também precisamos mudar a função main adaptando para que possamos usar a função escreverMundo como coroutineScope
fun main() = runBlocking {
    escreverMundo()
}
Agora, removemos o launch pois ele irá impedir que a main rode corretamente.
O resultado esperado do programa agora é:
Olá
Mundo!
Já se passaram 4 segundos né?
Agora vamos fazer uma experiência, vamos remover o println("Olá") na função escreverMundo, e vamos colocar no final da função main, dessa maneira:
import kotlinx.coroutines.*
fun main() = runBlocking {
    escreverMundo()
    println("Olá")
}
suspend fun escreverMundo() = coroutineScope {
    launch {
        delay(1000L)
        println("Mundo!")
    }
    launch{
        delay(4000L)
        println("Já se passaram 4 segundos né?")
    }
}
O resultado desse código é:
Mundo!
Já se passaram 4 segundos né?
Olá
- Como a função runBlockingbloqueia a thread enquanto está rodando, primeiro, todas as instruções deescreverMundosão rodadas, e após isso que o código irá continuar, mandando oOlána tela.
Com todos esses recursos, dá pra fazer bastante coisa usando escopos de corotinas com coroutineScope, iniciar partes do código com corotinas com runBlocking, iniciar uma corotina com launch, e fazer uma corotina esperar um tempo com delay.
Jobs (Tarefas)
Jobs ou tarefas são instâncias de corotinas, que podem ser manipuladas para por exemplo, cancelar a corotina, esperar a corotina terminar todo o processamento para que o código principal continue,etc. Vamos ver esse exemplo abaixo:
import kotlinx.coroutines.*
fun main() = runBlocking {
    val tarefa = launch {
        delay(1000L)
        println("Mundo!")
    }
    println("Olá")
    tarefa.join()
    println("Fim")
}
- 
mainrecebe um blocorunBlocking, podendo assim usar as corotinas dentro;
- é criada uma variável chamada tarefaque recebe uma corotina em um blocolaunch. Com isso, a corotina é iniciada e o código principal continua;
- Após isso, é escrito na tela um Olá;
- A função tarefa.join()faz com que a corotinatarefatenha de terminar para que o código principal continue, com isso a instruçãoprintln("Fim")apenas irá rodar depois da corotinatarefa
- Após isso, a corotina espera um segundo, com a instrução delay(1000L);
- E ao final da corotina tarefa, é escrito umMundo!na tela;
- E depois da corotina tarefater acabado, é escrito umFimna tela.
Com isso em mente, o output esperado é
Olá
Mundo!
Fim
Mas, e se eu quiser que a corotina
tarefarode junto com o código da funçãomain?
Podemos fazer isso mudando na linha 9 de tarefa.join() para tarefa.start(), com isso o nosso código ficará assim:
import kotlinx.coroutines.*
fun main() = runBlocking {
    val tarefa = launch {
        delay(1000L)
        println("Mundo!")
    }
    println("Olá")
    tarefa.start()
    println("Fim")
}
O output esperado dessa maneira é:
Olá
Fim
Mundo!
Isso acontece pois enquanto a função tarefa.join() suspende a thread (main no caso) enquanto roda, a função tarefa.start() apenas inicia uma corotina (no caso a corotina tarefa), e continua a rodar o código principal.
Cancelando tarefas
Agora vamos aprender a como cancelar uma tarefa, esse conhecimento é útil para aplicações que irão rodar por muito tempo sem parar, e vão precisar iniciar e fechar corotinas constantemente, como por exemplo, uma aplicação web feita em Ktor. (Caso você tenha interesse em Ktor, leia esse artigo da Kotlinautas Criando uma API com Ktor)
Primeiro, vamos criar uma main que recebe um runBlocking:
import kotlinx.coroutines.*
fun main() = runBlocking{
}
Agora, vamos criar uma variável tarefa que recebe um launch:
fun main() = runBlocking{
    val tarefa = launch {
        repeat(1000) { i ->
            println("tarefa: Estou rodando fazem $i vezes")
            delay(500L)
        }
    }
}
- A variável tarefarecebe umlaunch, logo sendo uma corotina;
- Dentro da corotina, há um repeat(1000), esserepeatinicia um código que irá rodar por um número determinado de vezes, no caso, 1000 vezes;
- E dentro desse bloco, é mostrado na tela um texto tarefa: Estou rodando fazem $i vezes, sendo$io número de vezes que orepeatjá repetiu;
- Depois desse texto ser mostrado na tela, a corotina é suspensa por 500 milesegundos (meio segundo);
Agora, vamos fazer que a main espere um tempo, escreva na tela que não deseja mais esperar que a corotina tarefa termine seu processamento, cancele a corotina tarefa, e feche a main em seguida;
import kotlinx.coroutines.*
fun main() = runBlocking{
    val tarefa = launch {
        repeat(1000) { i ->
            println("tarefa: Estou rodando fazem $i vezes")
            delay(500L)
        }
    }
    delay(1300L)
    println("main: Não quero mais esperar pela tarefa!")
    tarefa.cancel()
    tarefa.join()
    println("main: Agora eu posso fechar")
}
- Agora, a mainespera 1.3 segundos, e após isso, será mostrado na tela um textomain: Não quero mais esperar pela tarefa!;
- Após isso, é usada a função tarefa.cancel()para cancelar a corotina, fazendo a corotinatarefaterminar;
- Para fazer que o resto do código rode apenas quando a corotina for completamente cancelada, é usada a função tarefa.join()novamente;
- Após isso, a mainescreve na telamain: Agora eu posso fechar
O output esperado desse programa é:
tarefa: Estou rodando fazem 0 vezes
tarefa: Estou rodando fazem 1 vezes
tarefa: Estou rodando fazem 2 vezes
main: Não quero mais esperar pela tarefa!
main: Agora eu posso fechar
Segundo a própria documentação do Kotlin, a função .cancel() cancela a tarefa (corotina sendo armazenada em uma variável), incluindo todas as corotinas iniciadas por essa.
Mas não é toda corotina que pode ser cancelada dessa maneira, vamos ver o exemplo á seguir:
import kotlinx.coroutines.*
fun main() = runBlocking{
    val tarefa = launch {
        while (isActive) {
            println("tarefa: Estou rodando!")
            delay(1000L)
        }
    }
    delay(5000L)
    println("main: Não quero mais esperar pela tarefa!")
    tarefa.cancel()
    tarefa.join()
    println("main: Agora eu posso fechar")
}
- Agora, ao invés de um repeat(1000), temos umwhile(isActive),isActiveé uma variável interna da corotina, que sempre é verdadeira enquanto a corotina não terminou ou não foi cancelada. Logo, quando usamostarefa.cancel(), a variávelisActivese torna falsa e a corotina é cancelada.
O output esperado desse programa é:
tarefa: Estou rodando!
tarefa: Estou rodando!
tarefa: Estou rodando!
tarefa: Estou rodando!
tarefa: Estou rodando!
main: Não quero mais esperar pela tarefa!
main: Agora eu posso fechar
  
  
  Usando um try e finally dentro de uma corotina
Caso queiramos que a corotina faça algo antes de ser cancelada, podemos usar um bloco try com o código da corotina, e depois do try, dentro de um finally o código que irá rodar quando a corotina for cancelada.
Vamos usar o seguinte exemplo:
import kotlinx.coroutines.*
fun main() = runBlocking{
    val tarefa = launch {
        try {
                    var i = 0
                    while (isActive) {
                        println("tarefa: Estou rodando fazem $i vezes")
                        delay(1000L)
                        i++
                    }
                }finally {
                    println("tarefa: terminando corotina tarefa")
                }
    }
    delay(5000L)
    println("main: Não quero mais esperar pela tarefa!")
    tarefa.cancel()
    tarefa.join()
    println("main: Agora eu posso fechar")
}
- Agora, todo o código da corotina tarefaestá dentro de umtry, que é o mesmo código do exemplo anterior sobreisActive, mas agora, após otry, dentro de umfinally, mostramos na telatarefa: terminando corotina tarefa, mostrando esse conceito;
O output do programa é:
tarefa: Estou rodando fazem 0 vezes
tarefa: Estou rodando fazem 1 vezes
tarefa: Estou rodando fazem 2 vezes
tarefa: Estou rodando fazem 3 vezes
tarefa: Estou rodando fazem 4 vezes
main: Não quero mais esperar pela tarefa!
tarefa: terminando corotina tarefa
main: Agora eu posso fechar
as linha 18 e 19 podem ser refatoradas em uma só, pois há o método cancelAndJoin(), que cancela a corotina e espera pelo seu fechamento. Com isso, o nosso código ficará assim:
import kotlinx.coroutines.*
fun main() = runBlocking{
    val tarefa = launch {
        try {
            var i = 0
            while (isActive) {
                println("tarefa: Estou rodando fazem $i vezes")
                delay(1000L)
                i++
            }
        }finally {
            println("tarefa: terminando corotina tarefa")
        }
    }
    delay(5000L)
    println("main: Não quero mais esperar pela tarefa!")
    tarefa.cancelAndJoin()
    println("main: Agora eu posso fechar")
}
Timeout
É possível de criar corotinas com tempo máximo de existência, isso pode ser feito com withTimeout, informando um tempo do tipo Long, vamos supor o seguinte código:
import kotlinx.coroutines.*
fun main() = runBlocking{
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("Estou dormindo há $i ...")
            delay(500L)
        }
    }
}
Caso você tente rodar esse código, irá resultar neste erro:
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
    at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:186)
    at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:156)
    at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:497)
    at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
    at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:69)
    at java.base/java.lang.Thread.run(Thread.java:829)
Process finished with exit code 1
Nesse código, é usada a função withTimeout, que deixa fixo o tempo que uma corotina pode rodar. Caso esse tempo passe, é retornado um erro, sendo kotlinx.coroutines.TimeoutCancellationException.
Caso você queria que esse timeout não resulte em um erro, é possível se se usar a função withTimeoutOrNull, dessa maneira:
import kotlinx.coroutines.*
fun main() = runBlocking{
    val resultado = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("Estou dormindo $i ...")
            delay(500L)
        }
        "Feito"
    }
    println("Resultado é $resultado")
}
Com isso, caso esse timeout resulte em um erro, a variável resultado receberá o valor null, mas caso deletemos a linha 7, que é uma espera na corotina que aumenta elevadamente o tempo de processamento, ultrapassando o valor determinado de 1.3 segundos pelo withTimeoutOrNull o valor de resultado será Feito pois a corotina rodou sem problema nenhum. Dessa maneira, o código ficará assim:
import kotlinx.coroutines.*
fun main() = runBlocking{
    val resultado = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("Estou dormindo $i ...")
        }
        "Feito"
    }
    println("Resultado é $resultado")
}
Explorando mais sobre funções de suspensão
Vamos supor que temos duas funções, uma que retorna o número 10, e outra que retorna o número 20, e essas duas funções esperam por um segundo usando a função delay. Por conta dessas funções terem que pausar a sua execução, terão que ser funções de suspensão, tendo um suspend na frente. Dessa maneira:
suspend fun funçãoNúmeroUm(): Int {
    delay(1000L)
    return 10
}
suspend fun funçãoNúmeroDois(): Int {
    delay(1000L)
    return 20
}
Agora vamos criar uma main, que irá medir o tempo de execução total do código, criar duas variáveis, cada uma sendo o retorno dessas duas funções, e mostrar o resultado dessa maneira:
import kotlin.system.measureTimeMillis
fun main() = runBlocking {
    val tempo = measureTimeMillis {
        val um = funçãoNúmeroUm()
        val dois = funçãoNúmeroDois()
        println("A soma é ${um + dois}")
    }
    println("Feito em $tempo milisegundos")
}
- 
import kotlin.system.measureTimeMillisimporta a função que irá medir o tempo do código;
- O retorno das duas funções criadas anteriormente são armazenadas nas variáveis umedois;
- A soma dessas duas variáveis é mostrada na tela;
- O tempo total dessas operações é guardado na variável tempo;
- E o valor dessa variável tempoé mostrada na tela;
O output desse código será algo parecido com isso:
A soma é 30
Feito em 2008 milisegundos
E se eu quiser rodar essas duas funções ao mesmo tempo, economizando tempo de processamento?
Isso pode ser feito usando a função async. A função async inicia uma corotina como a função launch, mas que pode receber um valor como retorno. Por isso é interessante usar async nesses casos, pois poderemos guardar o retorno de funções de suspensão dentro de variáveis.
Vamos ver como a nossa função main ficará com a função async:
fun main() = runBlocking {
    val tempo = measureTimeMillis {
        val um = async { funçãoNúmeroUm() }
        val dois = async { funçãoNúmeroDois() }
        println("A soma é ${um.await() + dois.await()}")
    }
    println("Feito em $tempo milisegundos")
}
- Agora, as funções funçãoNúmeroUmefunçãoNúmeroDoisestão dentro deasync, instânciando uma nova corotina (tarefa) para cada função;
- Para pegar o valor de umedois, é usada a função.await(), que pega o resultado de dentro da corotina;
Agora, o código roda na metade do tempo pois as duas funções estão rodando ao mesmo tempo:
A soma é 30
Feito em 1015 milisegundos
  
  
  Estruturando concorrências com async
Podemos melhorar ainda mais o código acima, estruturando essa concorrência em uma função, dessa maneira:
suspend fun soma(): Int = coroutineScope {
    val um = async { funçãoNúmeroUm() }
    val dois = async { funçãoNúmeroDois() }
    um.await() + dois.await()
}
- Criamos uma função somaque é umcoroutineScope, esse escopo é muito interessante de ser usado nesse tipo de caso pois se uma corotina de dentro desse escopo falhar, todas as outras também irão falhar. No caso, as duas corotinas precisam dar um resultado válido para a função retornar o número coretamente.
- E o retorno da função pega o valor das variáveis umedois, e soma, retornando o resultado esperado de30.
Agora também podemos mudar a função main para usar a função soma:
fun main() = runBlocking {
    val tempo = measureTimeMillis {
        println("A soma é ${soma()}")
    }
    println("Feito em $tempo milisegundos")
}
Agora temos um código mais bem estruturado, seguro, e com seu output igual ainda:
A soma é 30
Feito em 1016 milisegundos
E se alguma corotina der um erro, como posso tratar esse erro usando
coroutineScope?
Vamos mudar a funçãoNúmeroDois para que essa função obrigatoriamente retorne um erro, dessa maneira:
suspend fun funçãoNúmeroDois(): Int {
    delay(1000L)
    return throw Exception("Função com erro esperado")
}
- Dessa maneira, obrigatoriamente, a funçãoNúmeroDoisretorna um erro do tipoFunção com erro esperado
Caso você tente rodar o código dessa maneira, dará um erro por conta da funçãoNúmeroDois:
Exception in thread "main" java.lang.Exception: Função com erro esperado
Para resolver isso, pode ser usado com bloco try com um catch, dessa maneira, tratando o erro. Vamos mudar a função main mas tratando o erro:
fun main() = runBlocking {
    try {
        val tempo = measureTimeMillis {
            println("A soma é ${soma()}")
        }
        println("Feito em $tempo milisegundos")
    }catch(erro: Exception){
        println("Ocorreu um erro: $erro")
    }
}
Agora, o output do programa é:
Ocorreu um erro: java.lang.Exception: Função com erro esperado
Mesmo que o erro Função com erro esperado tenha acontecido, a main fechou sem problemas, pois os blocos try e catch trataram o erro.
Finalização
Esse é o básico sobre corotinas no Kotlin. Há muito mais detalhes e conteúdos que podem ser abordados, mas para um artigo introdutório isso já é suficiente.
Muito obrigada por ler ❤️🏳️⚧️ e me segue nas redes, é tudo @lissatransborda 👀
 
 
              
 
                      


 
    
Top comments (0)