An Interest In:
Web News this Week
- March 20, 2024
- March 19, 2024
- March 18, 2024
- March 17, 2024
- March 16, 2024
- March 15, 2024
- March 14, 2024
Bsico de corotinas em Kotlin
Kotlinautas
Esse contedo oferecido e distribudo pela comunidade Kotlinautas, uma comunidade brasileira que busca oferecer contedo gratuito sobre a linguagem Kotlin em um espao plural.
O qu so corotinas?
Corotinas (ou Coroutines) so um bloco de cdigo que rodam concorrentemente com o resto do cdigo, isso significa que podemos rodar dois blocos de cdigo ao mesmo tempo, podendo assim ao mesmo tempo ler quanto enviar para um servidor por exemplo. Vamos ver mais sobre corotinas na prtica durante o artigo.
Materiais
Ser necessrio ter o IntelliJ instalado na mquina e um conhecimento bsico sobre a linguagem Kotlin.
Criando um projeto com Corotinas
Abra seu IntelliJ no menu inicial e clique em New Project:
Depois, selecione a opo Kotlin DSL build script, selecione tambm a opo Kotlin/JVM, e opicionalmente remova a primeira opo Java. Essa opo no vai mudar em nada, pois ela d suporte do Gradle linguagem Java, mas apenas iremos usar Kotlin.
Aps isso, clique em Next e escreva o nome do projeto e a localizao na sua mquina. Essas duas opo so completamente pessoais, caso no tenha nenhuma ideia, coloque algo como Corotinas apenas como identificao.
Agora, com o projeto aberto, v ao aquivo build.gradle.kts
e adicione a dependncia implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
, com a seo 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 alteraes no Gradle.
Aps isso, poderemos comear a programar. Voc pode criar um arquivo em src/main/kotlin/
chamado main.kt
para ser o arquivo principal da aplicao.
Mas com qualquer nome de arquivo, como voc ir usar as corotinas, sempre se lembre de importar a biblioteca de corotinas no comeo do arquivo:
import kotlinx.coroutines.*
Primeira Corotina
Vamos criar o primeiro exemplo, vamos criar uma corotina que ir rodar paralelamente com o cdigo principal, o cdigo principal apenas ir mostrar um "Ol", enquanto o cdigo da corotina ir esperar um segundo, e aps 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 cdigo no seu IntelliJ e rode. O output esperado desse cdigo esse:
OlMundo!
Agora vamos explicar o qu esse cdigo est fazendo:
RunBlocking
um bloco que ir armazenar todas as corotinas de uma parte do cdigo, como se criasse um contexto diferente do normal damain
. Todas as corotinas devem estar dentro de um blocorunBlocking
;launch
ir iniciar uma corotina, que ir funcionar concorrentemente (ao mesmo tempo) e independente do resto do cdigo, podemos inserir quantos blocoslaunch
que quisermos dentro de um mesmo cdigo;delay
uma funo que faz a corotina esperar por um tempo em milisegundos, e voltar com o processamento aps esse tempo. Essa funo recebe um nmero do tipoLong
, que pode ser criado colocando umL
no final de um nmero;
O runBlocking
guarda um launch
dentro, iniciando uma nova corotina, que a primeira instruo o delay(1000L)
, fazendo que a corotina espere por um segundo (1000 milisegundos), enquanto isso o cdigo principal continua, mandando um Ol
na tela. E aps um segundo da corotina rodando, a proxima e ultima instruo manda um Mundo!
na tela.
Refatorando para uma funo
Agora vamos transformar o contedo de dentro do bloco launch
em uma funo. Para isso, iremos precisar usar um suspend
antes da funo. (Funo de suspenso)
Mas, o qu esse
suspend
?
funes com suspend
so funes que podem ser usadas normalmente dentro de corotinas, mas podem usar algumas funes especiais, como a funo delay
que como foi explicado mais cedo, serve para fazer a corotina esperar um tempo em milisegundos.
Com isso em mente, vamos criar a funo:
suspend fun escreverMundo() { delay(1000L) println("Mundo!")}
E agora na main
, vamos tirar tudo de dentro do bloco launch
e rodar a funo escreverMundo()
dentro:
fun main() = runBlocking { launch { escreverMundo() } println("Ol")}
Pronto! Agora nosso cdigo est mais organizado, diminuindo o cdigo da funo main
.
Escopo de Corotinas
Podemos tambm criar um escopo onde iremos armazenar corotinas dentro. Esse escopo se chama coroutineScope
. Esse bloco muito parecido com o bloco runBlocking
, mas tem uma diferena, 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
coroutineScope
consegue fazer isso?
Porque o coroutineScope
uma funo de suspenso, enquanto o runBlocking
uma funo normal. Por isso coroutineScope
tem essas habilidades especiais.
Agora, vamos mudar a funo escreverMundo
, para fazer que essa funo 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 funo
escreverMundo
recebe umacoroutineScope
; - Como uma
coroutineScope
, podemos colocar vrios blocoslaunch
dentro. 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 instruo para escrever um
Ol
na tela.
Por conta que essas trs partes sero executadas ao mesmo tempo, primeiro ir aparecer Ol
, depois de um segundo Mundo!
, e depois de 4 segundos que o programa comeou a rodar, ir aparecer o J se passaram 4 segundos n?
.
Mas para que esse cdigo rode corretamente, tambm precisamos mudar a funo main
adaptando para que possamos usar a funo 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 :
OlMundo!J se passaram 4 segundos n?
Agora vamos fazer uma experincia, vamos remover o println("Ol")
na funo escreverMundo
, e vamos colocar no final da funo 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 cdigo :
Mundo!J se passaram 4 segundos n?Ol
- Como a funo
runBlocking
bloqueia a thread enquanto est rodando, primeiro, todas as instrues deescreverMundo
so rodadas, e aps isso que o cdigo ir continuar, mandando oOl
na tela.
Com todos esses recursos, d pra fazer bastante coisa usando escopos de corotinas com coroutineScope
, iniciar partes do cdigo com corotinas com runBlocking
, iniciar uma corotina com launch
, e fazer uma corotina esperar um tempo com delay
.
Jobs (Tarefas)
Jobs ou tarefas so instncias de corotinas, que podem ser manipuladas para por exemplo, cancelar a corotina, esperar a corotina terminar todo o processamento para que o cdigo 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")}
main
recebe um blocorunBlocking
, podendo assim usar as corotinas dentro;- criada uma varivel chamada
tarefa
que recebe uma corotina em um blocolaunch
. Com isso, a corotina iniciada e o cdigo principal continua; - Aps isso, escrito na tela um
Ol
; - A funo
tarefa.join()
faz com que a corotinatarefa
tenha de terminar para que o cdigo principal continue, com isso a instruoprintln("Fim")
apenas ir rodar depois da corotinatarefa
- Aps isso, a corotina espera um segundo, com a instruo
delay(1000L)
; - E ao final da corotina
tarefa
, escrito umMundo!
na tela; - E depois da corotina
tarefa
ter acabado, escrito umFim
na tela.
Com isso em mente, o output esperado
OlMundo!Fim
Mas, e se eu quiser que a corotina
tarefa
rode junto com o cdigo da funomain
?
Podemos fazer isso mudando na linha 9 de tarefa.join()
para tarefa.start()
, com isso o nosso cdigo 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 :
OlFimMundo!
Isso acontece pois enquanto a funo tarefa.join()
suspende a thread (main
no caso) enquanto roda, a funo tarefa.start()
apenas inicia uma corotina (no caso a corotina tarefa
), e continua a rodar o cdigo principal.
Cancelando tarefas
Agora vamos aprender a como cancelar uma tarefa, esse conhecimento til para aplicaes que iro rodar por muito tempo sem parar, e vo precisar iniciar e fechar corotinas constantemente, como por exemplo, uma aplicao 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 varivel tarefa
que recebe um launch
:
fun main() = runBlocking{ val tarefa = launch { repeat(1000) { i -> println("tarefa: Estou rodando fazem $i vezes") delay(500L) } }}
- A varivel
tarefa
recebe umlaunch
, logo sendo uma corotina; - Dentro da corotina, h um
repeat(1000)
, esserepeat
inicia um cdigo que ir rodar por um nmero determinado de vezes, no caso, 1000 vezes; - E dentro desse bloco, mostrado na tela um texto
tarefa: Estou rodando fazem $i vezes
, sendo$i
o nmero de vezes que orepeat
j 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 no 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: No quero mais esperar pela tarefa!") tarefa.cancel() tarefa.join() println("main: Agora eu posso fechar")}
- Agora, a
main
espera 1.3 segundos, e aps isso, ser mostrado na tela um textomain: No quero mais esperar pela tarefa!
; - Aps isso, usada a funo
tarefa.cancel()
para cancelar a corotina, fazendo a corotinatarefa
terminar; - Para fazer que o resto do cdigo rode apenas quando a corotina for completamente cancelada, usada a funo
tarefa.join()
novamente; - Aps isso, a
main
escreve na telamain: Agora eu posso fechar
O output esperado desse programa :
tarefa: Estou rodando fazem 0 vezestarefa: Estou rodando fazem 1 vezestarefa: Estou rodando fazem 2 vezesmain: No quero mais esperar pela tarefa!main: Agora eu posso fechar
Segundo a prpria documentao do Kotlin, a funo .cancel()
cancela a tarefa (corotina sendo armazenada em uma varivel), incluindo todas as corotinas iniciadas por essa.
Mas no 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: No quero mais esperar pela tarefa!") tarefa.cancel() tarefa.join() println("main: Agora eu posso fechar")}
- Agora, ao invs de um
repeat(1000)
, temos umwhile(isActive)
,isActive
uma varivel interna da corotina, que sempre verdadeira enquanto a corotina no terminou ou no foi cancelada. Logo, quando usamostarefa.cancel()
, a varivelisActive
se 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: No quero mais esperar pela tarefa!main: Agora eu posso fechar
Usando um try
e finally
dentro de uma corotina
Caso queiramos que a corotina faa algo antes de ser cancelada, podemos usar um bloco try
com o cdigo da corotina, e depois do try
, dentro de um finally
o cdigo 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: No quero mais esperar pela tarefa!") tarefa.cancel() tarefa.join() println("main: Agora eu posso fechar")}
- Agora, todo o cdigo da corotina
tarefa
est dentro de umtry
, que o mesmo cdigo do exemplo anterior sobreisActive
, mas agora, aps otry
, dentro de umfinally
, mostramos na telatarefa: terminando corotina tarefa
, mostrando esse conceito;
O output do programa :
tarefa: Estou rodando fazem 0 vezestarefa: Estou rodando fazem 1 vezestarefa: Estou rodando fazem 2 vezestarefa: Estou rodando fazem 3 vezestarefa: Estou rodando fazem 4 vezesmain: No quero mais esperar pela tarefa!tarefa: terminando corotina tarefamain: Agora eu posso fechar
as linha 18 e 19 podem ser refatoradas em uma s, pois h o mtodo cancelAndJoin()
, que cancela a corotina e espera pelo seu fechamento. Com isso, o nosso cdigo 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: No quero mais esperar pela tarefa!") tarefa.cancelAndJoin() println("main: Agora eu posso fechar")}
Timeout
possvel de criar corotinas com tempo mximo de existncia, isso pode ser feito com withTimeout
, informando um tempo do tipo Long
, vamos supor o seguinte cdigo:
import kotlinx.coroutines.*fun main() = runBlocking{ withTimeout(1300L) { repeat(1000) { i -> println("Estou dormindo h $i ...") delay(500L) } }}
Caso voc tente rodar esse cdigo, 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 cdigo, usada a funo 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 no resulte em um erro, possvel se se usar a funo 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 varivel 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 cdigo 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 funes de suspenso
Vamos supor que temos duas funes, uma que retorna o nmero 10
, e outra que retorna o nmero 20
, e essas duas funes esperam por um segundo usando a funo delay
. Por conta dessas funes terem que pausar a sua execuo, tero que ser funes de suspenso, tendo um suspend
na frente. Dessa maneira:
suspend fun funoNmeroUm(): Int { delay(1000L) return 10}suspend fun funoNmeroDois(): Int { delay(1000L) return 20}
Agora vamos criar uma main
, que ir medir o tempo de execuo total do cdigo, criar duas variveis, cada uma sendo o retorno dessas duas funes, e mostrar o resultado dessa maneira:
import kotlin.system.measureTimeMillisfun main() = runBlocking { val tempo = measureTimeMillis { val um = funoNmeroUm() val dois = funoNmeroDois() println("A soma ${um + dois}") } println("Feito em $tempo milisegundos")}
import kotlin.system.measureTimeMillis
importa a funo que ir medir o tempo do cdigo;- O retorno das duas funes criadas anteriormente so armazenadas nas variveis
um
edois
; - A soma dessas duas variveis mostrada na tela;
- O tempo total dessas operaes guardado na varivel
tempo
; - E o valor dessa varivel
tempo
mostrada na tela;
O output desse cdigo ser algo parecido com isso:
A soma 30Feito em 2008 milisegundos
E se eu quiser rodar essas duas funes ao mesmo tempo, economizando tempo de processamento?
Isso pode ser feito usando a funo async
. A funo async
inicia uma corotina como a funo launch
, mas que pode receber um valor como retorno. Por isso interessante usar async
nesses casos, pois poderemos guardar o retorno de funes de suspenso dentro de variveis.
Vamos ver como a nossa funo main
ficar com a funo async
:
fun main() = runBlocking { val tempo = measureTimeMillis { val um = async { funoNmeroUm() } val dois = async { funoNmeroDois() } println("A soma ${um.await() + dois.await()}") } println("Feito em $tempo milisegundos")}
- Agora, as funes
funoNmeroUm
efunoNmeroDois
esto dentro deasync
, instnciando uma nova corotina (tarefa) para cada funo; - Para pegar o valor de
um
edois
, usada a funo.await()
, que pega o resultado de dentro da corotina;
Agora, o cdigo roda na metade do tempo pois as duas funes esto rodando ao mesmo tempo:
A soma 30Feito em 1015 milisegundos
Estruturando concorrncias com async
Podemos melhorar ainda mais o cdigo acima, estruturando essa concorrncia em uma funo, dessa maneira:
suspend fun soma(): Int = coroutineScope { val um = async { funoNmeroUm() } val dois = async { funoNmeroDois() } um.await() + dois.await()}
- Criamos uma funo
soma
que umcoroutineScope
, esse escopo muito interessante de ser usado nesse tipo de caso pois se uma corotina de dentro desse escopo falhar, todas as outras tambm iro falhar. No caso, as duas corotinas precisam dar um resultado vlido para a funo retornar o nmero coretamente. - E o retorno da funo pega o valor das variveis
um
edois
, e soma, retornando o resultado esperado de30
.
Agora tambm podemos mudar a funo main
para usar a funo soma
:
fun main() = runBlocking { val tempo = measureTimeMillis { println("A soma ${soma()}") } println("Feito em $tempo milisegundos")}
Agora temos um cdigo mais bem estruturado, seguro, e com seu output igual ainda:
A soma 30Feito em 1016 milisegundos
E se alguma corotina der um erro, como posso tratar esse erro usando
coroutineScope
?
Vamos mudar a funoNmeroDois
para que essa funo obrigatoriamente retorne um erro, dessa maneira:
suspend fun funoNmeroDois(): Int { delay(1000L) return throw Exception("Funo com erro esperado")}
- Dessa maneira, obrigatoriamente, a
funoNmeroDois
retorna um erro do tipoFuno com erro esperado
Caso voc tente rodar o cdigo dessa maneira, dar um erro por conta da funoNmeroDois
:
Exception in thread "main" java.lang.Exception: Funo com erro esperado
Para resolver isso, pode ser usado com bloco try
com um catch
, dessa maneira, tratando o erro. Vamos mudar a funo 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: Funo com erro esperado
Mesmo que o erro Funo com erro esperado
tenha acontecido, a main
fechou sem problemas, pois os blocos try
e catch
trataram o erro.
Finalizao
Esse o bsico sobre corotinas no Kotlin. H muito mais detalhes e contedos que podem ser abordados, mas para um artigo introdutrio isso j suficiente.
Obrigado por ler
Original Link: https://dev.to/kotlinautas/basico-de-corotinas-em-kotlin-50a8
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To