Provavelmente, você já lidou com problemas comuns, como falta de thread, retorno de chamada e bloqueios de threads por mais tempo do que deveriam durante sua carreira como programador. O fato é que trabalhar com threads não é tão fácil, especialmente se você está direcionando rotinas de moda assíncrona para seus sistemas.
Muitas linguagens desenvolveram simplificações para codificação assíncrona-como Goroutines de Go, que são basicamente threads leves gerenciados pelo tempo de execução Go. Recursos semelhantes são fornecidos pelo Closure com seus recursos core.async para programação assíncrona, por Node.js com o notório evento loop, e agora Kotlin com corrotinas .
Neste artigo, exploraremos o universo emergente das corrotinas Kotlin em uma tentativa de demonstrar como elas podem simplificar sua programação assíncrona dentro da linguagem.
O que torna as corrotinas Kotlin únicas?
Kotlin não possui recursos assíncronos padrão que outras linguagens possuem, como as palavras reservadas integradas para async
e await
em JavaScript. Em vez disso, a JetBrains lançou um novo conjunto de corrotinas na biblioteca kotlinx-coroutines com várias corrotinas de alto nível para várias tarefas, como iniciar
e async
, entre outras.
Dê uma olhada no exemplo a seguir extraído do ambiente playground que a JetBrains oferece:
suspend fun main ()=coroutineScope { lançar { atraso (1000) println ("Kotlin Coroutines World!") } println ("Olá") }
Qual linha de impressão você acha que será impressa primeiro? Você está certo se sua resposta foi “Olá”. Isso acontece porque estamos atrasando o bloco launch
por um segundo, enquanto a segunda impressão não.
Basicamente, uma co-rotina nada mais é do que um fio simples e leve. Como costumávamos fazer com o Java, eles precisam ser iniciados explicitamente, o que você pode fazer por meio do construtor de co-rotina launch
no contexto de um coroutineScope
(por exemplo, em global escopo, a co-rotina vive tanto quanto o aplicativo vive).
O construtor coroutineScope
cria um escopo de corrotina que espera que todas as suas corrotinas filhas sejam concluídas antes de realizar sua própria conclusão.
Esse é um ótimo recurso para aqueles que desejam agrupar diferentes corrotinas em uma mais global. E é um conceito muito semelhante ao runBlocking
, que bloqueia o encadeamento atual para aguardar no modo de suspensão apenas que o coroutineScope
ativa.
Em nosso exemplo acima, a função delay
usa o escopo Thread
e pode ser substituída por:
launch { Thread.sleep (1000) println ("Kotlin Coroutines World!") }
A função launch
, por sua vez, pode ser substituída pela função equivalente Thread
.
Tenha cuidado ao alterá-lo no exemplo porque a função delay
, que também é uma função suspend
, só pode ser chamada de uma co-rotina ou outra suspend função
.
Com base nesses termos, nosso exemplo de código migraria para o seguinte:
import kotlinx.coroutines. * import kotlin.concurrent.thread suspend fun main ()=coroutineScope { fio { Thread.sleep (1000) println ("Kotlin Coroutines World!") } println ("Olá") }
Uma grande vantagem das corrotinas é que elas podem suspender sua execução no thread em que estão executando quantas vezes quiserem. Isso significa que economizamos muito em termos de recursos, porque os threads infinitos interrompidos aguardando a conclusão da execução não são mais a regra.
Se quiser esperar a conclusão de uma co-rotina específica, você também pode fazer isso:
val job=GlobalScope.launch { atraso (1000L) println ("Corrotinas!") } println ("Olá,") job.join ()
A referência que estamos criando aqui é conhecida como plano de fundo job , que é uma tarefa cancelável com um ciclo de vida que culmina em sua conclusão. A função join
espera até que a co-rotina seja concluída.
É um conceito muito útil para empregar nos casos em que você gostaria de ter mais controle sobre o estado síncrono da conclusão de algumas corrotinas. Mas como Kotlin consegue isso?
Estilo de aprovação contínua
CPS , ou estilo de continuation-pass, é um tipo de programação que funciona permitindo que o fluxo de controle seja passado explicitamente na forma de uma continuação-isto é, como uma representação abstrata do estado de controle de um fluxo de programa de computador. É muito semelhante à famosa função de retorno de chamada em JavaScript.
Para entender melhor, vamos dar uma olhada em Continuação
:
interface Continuação { val context: CoroutineContext currículo divertido (valor: T) currículo divertido com (resultado: Resultado) divertido resumeWithException (exceção: Throwable) }
Isso representa uma continuação após um ponto de suspensão que retorna um valor do tipo T
. Entre seus principais objetos e funções, temos:
-
contexto
: o link de contexto para essa continuação -
resumeXXX
: funções para retomar a execução da corrotina correspondente com resultados diferentes
Ótimo! Agora, vamos passar para um exemplo mais prático. Imagine que você esteja lidando com uma função comum que recupera informações de seu banco de dados por meio de uma função de suspensão:
suspend fun slowQueryById (id: Int): Data { atraso (1000) dados de retorno (id=id,...) }
Digamos que a função delay
emule a consulta lenta que você precisa executar para obter os resultados dos dados.
Nos bastidores, Kotlin converte a corrotina em uma espécie de função de retorno de chamada por meio de outro conceito conhecido como máquina de estado , em vez de criar várias funções novas.
Cancelamentos vs. tempos limite
Já aprendemos como criar trabalhos em segundo plano e como esperar até que sejam concluídos. Também vimos que essas tarefas são estruturas canceláveis, o que significa que, em vez de esperar que sejam concluídas, você pode cancelá-las se não estiver mais interessado nos resultados.
Nessa situação, basta chamar a função cancelar
:
job.cancel ()
No entanto, também haverá momentos em que você deseja estabelecer um limite para certas operações antes de cancelá-las ou esperar que sejam concluídas. É aí que os tempos limite se tornam úteis.
Se uma determinada operação demorar mais do que deveria, a configuração de timeout
garantirá o lançamento de uma exceção apropriada para você reagir de acordo:
runBlocking { withTimeout (2000L) { repetir (100) { atraso (500L) } } }
Se a operação exceder o limite de tempo que definimos de dois segundos, um erro CancelamentoException
será gerado.
Outra versão disso é possível por meio do bloco withTimeoutOrNull
. Vejamos um exemplo:
import kotlinx.coroutines. * suspend fun main ()=runBlocking{ withTimeoutOrNull (350) { para (i em 1..5) { atraso (100) println ("Número atual: $ i") } } }
Aqui, apenas os números de um a três serão impressos porque o tempo limite está definido para 350 ms. Temos um atraso de 100 ms para cada iteração, o que é suficiente apenas para preencher três valores do nosso for
.
Isso também é bom para os cenários em que você não deseja que exceções sejam lançadas.
Assinatura
Se você já trabalhou com JavaScript antes, pode estar acostumado a criar funções async
e a aguardá-las
quando os resultados são esperados em um bloco síncrono.
O Kotlin nos permite fazer o mesmo por meio da corrotina async
. Digamos que você queira iniciar dois threads de processamento rígido diferentes e esperar que ambos os resultados retornem ao thread principal. Abaixo está um exemplo que expõe como o Kotlin usa recursos do Java, como Futuro
:
val thread1=assíncrono (CommonPool) { //processamento difícil 1 } val thread2=assíncrono (CommonPool) { //processamento difícil 2 } runBlocking { thread1.await () thread2.await () }
A função async
cria uma nova co-rotina e retorna seu resultado futuro como uma implementação de Deferred
. A corrotina em execução é cancelada quando o Adiado
resultante é cancelado.
Adiado
, por sua vez, é um futuro cancelável sem bloqueio-ou seja, é um Job
que tem um resultado.
Quando as duas corrotinas de processamento rígido começam, a corrotina principal é suspensa por meio da chamada de execução runBlocking
e será retomada somente depois que os dois resultados da thread estiverem disponíveis. Dessa forma, ganhamos em desempenho, pois ambas as corrotinas serão executadas em paralelo.
Criação de fluxos para fluxos de dados assíncronos
O Kotlin também nos oferece uma ótima maneira de lidar com fluxos de dados assíncronos. Às vezes, você precisará de seus fluxos para emitir valores, convertê-los por meio de algumas funções assíncronas externas, coletar os resultados e concluir o fluxo com êxito ou com exceções.
Se for esse o caso, podemos usar o Tipo de fluxo
. Vamos pegar o seguinte exemplo que itera sobre uma sequência numérica e imprime cada um de seus valores:
import kotlinx.coroutines. * import kotlinx.coroutines.flow. * suspend fun main ()=runBlocking{ (1..3).asFlow (). Collect {value-> println ("Número atual: $ value")} }
Se você está acostumado a usar a API Java Streams ou versões semelhantes de outras linguagens, este código pode ser muito familiar para você.
O Kotlin também oferece funções auxiliares para operações mapear
e filtrar
, embora possam ter chamadas assíncronas de longa duração em:
import kotlinx.coroutines. * import kotlinx.coroutines.flow. * suspend fun main ()=runBlocking{ (1..5).asFlow () .filter {number-> number% 2==0}//apenas números pares .map {número-> convertToStr (número)}//converte para string .collect {value-> println (value)} } suspend fun convertToStr (solicitação: Int): String { atraso (1000) return"Número atual: $ request" }
Conclusão
É ótimo ver Kotlin dando um passo adiante em direção à criação de um mundo mais assíncrono e não bloqueador. Embora as corrotinas Kotlin sejam relativamente novas, elas já capitalizam o grande potencial que outras linguagens vêm extraindo desse paradigma há muito tempo.
A postagem Compreendendo as corrotinas do Kotlin apareceu primeiro em LogRocket Blog .