Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 24, 2021 11:22 am GMT

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.

capa Kotlinautas

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:

boto New Project no menu inicial do IntelliJ

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.

Elefante do Gradle no canto superior direito

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 da main. Todas as corotinas devem estar dentro de um bloco runBlocking;
  • launch ir iniciar uma corotina, que ir funcionar concorrentemente (ao mesmo tempo) e independente do resto do cdigo, podemos inserir quantos blocos launch 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 tipo Long, que pode ser criado colocando um L 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 uma coroutineScope;
  • Como uma coroutineScope, podemos colocar vrios blocos launch 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 de escreverMundo so rodadas, e aps isso que o cdigo ir continuar, mandando o Ol 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 bloco runBlocking, podendo assim usar as corotinas dentro;
  • criada uma varivel chamada tarefa que recebe uma corotina em um bloco launch. 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 corotina tarefa tenha de terminar para que o cdigo principal continue, com isso a instruo println("Fim") apenas ir rodar depois da corotina tarefa
  • Aps isso, a corotina espera um segundo, com a instruo delay(1000L);
  • E ao final da corotina tarefa, escrito um Mundo! na tela;
  • E depois da corotina tarefa ter acabado, escrito um Fim 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 funo main?

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 um launch, logo sendo uma corotina;
  • Dentro da corotina, h um repeat(1000), esse repeat 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 o repeat 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 texto main: No quero mais esperar pela tarefa!;
  • Aps isso, usada a funo tarefa.cancel() para cancelar a corotina, fazendo a corotina tarefa 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 tela main: 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 um while(isActive), isActive uma varivel interna da corotina, que sempre verdadeira enquanto a corotina no terminou ou no foi cancelada. Logo, quando usamos tarefa.cancel(), a varivel isActive 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 um try, que o mesmo cdigo do exemplo anterior sobre isActive, mas agora, aps o try, dentro de um finally, mostramos na tela tarefa: 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 e dois;
  • 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 e funoNmeroDois esto dentro de async, instnciando uma nova corotina (tarefa) para cada funo;
  • Para pegar o valor de um e dois, 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 um coroutineScope, 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 e dois, e soma, retornando o resultado esperado de 30.

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 tipo Funo 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

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To