Básico de corotinas em Kotlin
Tags: kotlin, backend
Published: 2021-08-24 11:22:10

## Kotlinautas

Esse conteúdo é oferecido e distribuído pela comunidade [Kotlinautas](https://twitter.com/kotlinautas/), uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.

![capa Kotlinautas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jreawpvk6whxigcpmctf.png)

## 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](https://www.jetbrains.com/idea/) instalado na máquina e um conhecimento básico sobre a linguagem [Kotlin](https://kotlinlang.org/).

## Criando um projeto com Corotinas

Abra seu IntelliJ no menu inicial e clique em *New Project*:

![botão New Project no menu inicial do IntelliJ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cyipebjncu4l9q33v0w1.png)

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:

```kotlin
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.

![Elefante do Gradle no canto superior direito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkf14hqf488c3wjt9uty.png)

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:

```kotlin
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:

```kotlin
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 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 código, podemos inserir quantos blocos `launch` que 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 tipo `Long`, que pode ser criado colocando um `L` no 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:

```kotlin
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:

```kotlin
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 `coroutineScope` consegue 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`:

```kotlin
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 `escreverMundo` recebe uma `coroutineScope`;
- Como uma `coroutineScope`, podemos colocar vários 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 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`

```kotlin
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:

```kotlin
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 `runBlocking` bloqueia a thread enquanto está rodando, primeiro, todas as instruções de `escreverMundo` são rodadas, e após isso que o código 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 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:

```kotlin
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 variável chamada `tarefa` que recebe uma corotina em um bloco `launch`. 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 corotina `tarefa` tenha de terminar para que o código principal continue, com isso a instrução `println("Fim")` apenas irá rodar depois da corotina `tarefa`
- Após isso, a corotina espera um segundo, com a instrução `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 é

```
Olá
Mundo!
Fim
```

> Mas, e se eu quiser que a corotina `tarefa` rode junto com o código da função `main`?

Podemos fazer isso mudando na linha 9 de `tarefa.join()` para `tarefa.start()`, com isso o nosso código ficará assim:

```kotlin
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](https://ktor.io/). (Caso você tenha interesse em Ktor, leia esse artigo da Kotlinautas [Criando uma API com Ktor](https://dev.to/kotlinautas/criando-uma-api-com-ktor-8le))

Primeiro, vamos criar uma `main` que recebe um `runBlocking`:

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking{

}
```

Agora, vamos criar uma variável `tarefa` que recebe um `launch`:

```kotlin
fun main() = runBlocking{
    val tarefa = launch {
        repeat(1000) { i ->
            println("tarefa: Estou rodando fazem $i vezes")
            delay(500L)
        }
    }
}
```

- A variável `tarefa` recebe um `launch`, logo sendo uma corotina;
- Dentro da corotina, há um `repeat(1000)`, esse `repeat` inicia 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 `$i` o número 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 não deseja mais esperar que a corotina `tarefa` termine seu processamento, cancele a corotina `tarefa`, e feche a `main` em seguida;

```kotlin
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 `main` espera 1.3 segundos, e após isso, será mostrado na tela um texto `main: Não quero mais esperar pela tarefa!`;
- Após isso, é usada a função `tarefa.cancel()` para cancelar a corotina, fazendo a corotina `tarefa` terminar;
- 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 `main` escreve na tela `main: 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](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html), 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:

```kotlin
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 um `while(isActive)`, `isActive` é uma variável interna da corotina, que sempre é verdadeira enquanto a corotina não terminou ou não foi cancelada. Logo, quando usamos `tarefa.cancel()`, a variável `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: 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:

```kotlin
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 `tarefa` está dentro de um `try`, que é o mesmo código do exemplo anterior sobre `isActive`, mas agora, após 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 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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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.measureTimeMillis` importa 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 `um` e `dois`;
- 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`:

```kotlin
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úmeroUm` e `funçãoNúmeroDois` estão dentro de `async`, instânciando uma nova corotina (tarefa) para cada função;
- Para pegar o valor de `um` e `dois`, é 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:

```kotlin
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 `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 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 `um` e `dois`, e soma, retornando o resultado esperado de `30`.

Agora também podemos mudar a função `main` para usar a função `soma`:

```kotlin
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:

```kotlin
suspend fun funçãoNúmeroDois(): Int {
	delay(1000L)
	return throw Exception("Função com erro esperado")
}
```

- Dessa maneira, obrigatoriamente, a `funçãoNúmeroDois` retorna um erro do tipo `Funçã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:

```kotlin
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](https://twitter.com/lissatransborda) 👀