Coroutines são rotinas cooperativas que podem suspender sua execução para permitir que outras rotinas sejam executadas, permitindo a sensação de execução paralela em sistemas com um único processador e thread. No Kotlin, coroutines usam funções suspensas e contextos para gerenciar estado e execução. Canais permitem a comunicação assíncrona entre coroutines de forma semelhante a filas bloqueantes, mas suspensas.
1. Coroutines:
O que são, como funcionam e como utilizá-las
Paulo Sato • Master Engineer • psato@ciandt.com
2. Paulo Sato
Desenvolvedor Android há 10 anos
Está com a CI&T há 3 anos
Gosto de ensinar pessoas e adoro aprender
coisas novas!
linkedin.com/in/paulovsato
5. Coroutine
● São rotinas cooperativas, ou seja, rotinas (funções, métodos,
procedimentos) que concordam em parar sua execução,
permitindo que outra rotina possa ser executada naquele
momento e esperando que essa rotina secundária devolva a
execução para ela em algum momento
● Dessa forma, uma rotina coopera com a outra
● Criada para dar a sensação de execução paralela em sistemas com um
processador e apenas uma thread
8. Suspend Functions
● No Kotlin, para uma função poder parar no meio, não precisamos
mais chamar o yield (que é uma Suspend Function); qualquer
chamada para uma Suspend Function pode parar a execução de
uma Coroutine
● Funcionam utilizando Continuations para guardar o estado da
execução
10. Coroutine Context
● Coroutines sempre são executadas em um contexto representado
pelo valor da CoroutineContext, que é definido na própria
biblioteca
● O contexto das Coroutines pode ser pensado como um
agrupamento de alguns elementos. Os principais elementos são
o Job e o Dispatcher
11. Job
● Conceitualmente, um trabalho é algo cancelável com um ciclo de
vida que culmina em sua completude
● Toda Coroutine roda em um Job. Você pode especificar um ou
um novo será criado quando uma nova Coroutine for lançada. O
Job é utilizado principalmente para fazer a junção de Coroutines
e cancelamentos
12. Dispatcher
● O CoroutineDispatcher determina qual thread, ou threads, que a
Coroutine utiliza para sua execução. O Dispatcher pode confinar
a execução a uma Thread específica, disparar a um ThreadPool,
ou deixar ela rodar sem confinamento (que normalmente não é
utilizado)
● Dispatcher.Default: Utiliza um pool comum de thread. Deve ser utilizado para tarefas
com computação intensiva
● Dispatcher.IO: Utiliza um pool compartilhado de thread criado sob demanda, que foi
feito para operações bloqueantes
● Dispatcher.Main: Apenas para Android, serve para executar a Coroutine na main thread
(atualizar UI)
13. Launch
● Lança uma nova Coroutine, sem bloquear a thread atual.
Retornando um Job, a Coroutine será cancelada quando um Job
for cancelado
14. Exemplo - Launch
val request = launch {
// it spawns two other jobs, one with GlobalScope
GlobalScope.launch {
}
// and the other inherits the parent context
launch {
}
}
delay(500)
request.cancel() // cancel processing of the request
delay(1000) // delay a second to see what happens
println("main: Who has survived request cancellation?")
delay(100)
println("job2: I am a child of the request coroutine")
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled")
println("job1: I run in GlobalScope and execute independently!")
delay(1000)
println("job1: I am not affected by cancellation of the request")
15. Resultado
job1: I run in GlobalScope and execute independently!
job2: I am a child of the request coroutine
job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?
16. withContext
● Chama um bloco de código com o contexto passado como
parâmetro, suspendendo a execução atual até que o bloco
complete sua execução e retorne seu resultado
● Muito utilizado em Android para executar operações em
background e atualizar a UI com o resultado
17. Exemplo - withContext
parentJob = CoroutineScope(Dispatchers.Main + exceptionHandler).launch {
val result = withContext(Dispatchers.IO) {
executeOnBackground(request)
}
response(result)
}
18. Async
● Cria uma Coroutine e retorna o futuro resultado como um
Deferred. A Coroutine que está rodando será cancelada caso o
resultado do Deferred seja cancelado
● A Coroutine gerada no Async tem uma diferença chave
comparada com outras primitivas em outras linguagens e
frameworks: ela cancela o Job pai em falha para garantir um
paradigma estruturado de concorrência
19. Exemplo - Async
log("Started main coroutine")
// run two background value computations
val v1 = async(CoroutineName("v1coroutine")) {
}
val v2 = async(CoroutineName("v2coroutine")) {
}
log("The answer for v1 / v2 = ${v1.await() / }")
delay(500)
log("Computing v1")
252
delay(1000)
log("Computing v2")
6
v2.await()
20. Resultado
[main @main#1] Started main coroutine
[main @v1coroutine#2] Computing v1
[main @v2coroutine#3] Computing v2
[main @main#1] The answer for v1 / v2 = 42
21. Deferred
● O Deferred<T> é uma classe genérica que representa uma
promessa de entregar um resultado no futuro. É necessário
chamar a suspend function await no objeto deferred para
conseguir o resultado eventualmente.
● A classe Deferred estende a class Job, portanto pode ser
utilizada para cancelar a Courtines filhas.
26. Mutex
● Exclusão mútua para Coroutines
● Mutex tem dois estados: trancado e aberto
● É não reentrante; isso significa que chamar o lock da mesma
thread/Coroutine que atualmente tem a chave ainda faz com que
o invocante fique suspendido
29. ● Um Channel é conceitualmente similar a uma BlockingQueue.
Duas diferenças chave entre eles:
● Ao invés da operação bloqueante put, ele tem a operação send, que suspende a
Coroutine
● Ao invés da operação bloqueante take, ele tem o receive, que suspende a
Coroutine
● É possível implementar back pressure para Coroutines com
Pipelines (Channels que podem emitir infinitos valores)
Channels
30. Code - Pipeline
fun main() = runBlocking {
val numbers = produceNumbers() // produces integers from 1 and on
val squares = square(numbers) // squares integers
for (i in 1..5) println(squares.receive()) // print first five
}
fun CoroutineScope.produceNumbers() = produce<Int> {
}
fun CoroutineScope.square(numbers: ReceiveChannel<Int>):
ReceiveChannel<Int> = produce {
}
1
4
9
16
25
Done!var x = 1
while (true) send(x++) // infinite stream of integers starting from 1
delay(1000)
for (x in numbers) send(x * x)
println("Done!") // we are done
coroutineContext.cancelChildren() // cancel children coroutines