Coroutines:
O que são, como funcionam e como utilizá-las
Paulo Sato • Master Engineer • psato@ciandt.com
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
Introdução a
Coroutines
Mas afinal, o que é uma Coroutine?
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
Coroutine
Kotlin
Coroutines
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
Código Kotlin
interface ShowRepository {
@Throws(IOException::class)
suspend fun searchShow(query: String):
List<ShowInfo>
@Throws(IOException::class)
suspend fun showRating(id: String): Rating
}
@Metadata
public interface ShowRepository {
@Nullable
Object searchShow(@NotNull String var1, @NotNull Continuation var2)
throws IOException;
@Nullable
Object showRating(@NotNull String var1, @NotNull Continuation var2)
throws IOException;
}
Código gerado
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
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
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)
Launch
● Lança uma nova Coroutine, sem bloquear a thread atual.
Retornando um Job, a Coroutine será cancelada quando um Job
for cancelado
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")
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?
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
Exemplo - withContext
parentJob = CoroutineScope(Dispatchers.Main + exceptionHandler).launch {
val result = withContext(Dispatchers.IO) {
executeOnBackground(request)
}
response(result)
}
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
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()
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
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.
Sincronização
Synchronized
● Synchronized não funciona em
todos os casos para Coroutines
Exemplo - Synchronized
fun main() = runBlocking<Unit> {
launch {
val result1 = async {
dosomething(1)
}
val result2 = async {
dosomething(2)
}
result1.await()
result2.await()
println("main: finished")
}
}
@Synchronized suspend fun dosomething(thread : Int){
println("Start synchronized block $thread ")
for(i in 0..5){
delay(100)
println("step synchronized block $thread")
}
println("End synchronized block $thread")
}
Resultado
Start synchronized block 1
Start synchronized block 2
step synchronized block 1
step synchronized block 2
step synchronized block 1
step synchronized block 2
step synchronized block 1
step synchronized block 2
step synchronized block 1
step synchronized block 2
step synchronized block 1
step synchronized block 2
step synchronized block 1
End synchronized block 1
step synchronized block 2
End synchronized block 2
main: finished
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
Exemplo - Mutex
val mutex = Mutex(false)
fun main() = runBlocking<Unit> {
launch {
val result1 = async {
dosomething(1)
}
val result2 = async {
dosomething(2)
}
result1.await()
result2.await()
println("main: finished")
}
}
suspend fun dosomething(thread : Int){
mutex.lock()
println("Start synchronized block $thread ")
for(i in 0..5){
delay(100)
println("step synchronized block $thread")
}
println("End synchronized block $thread")
mutex.unlock()
}
Resultado
Start synchronized block 1
step synchronized block 1
step synchronized block 1
step synchronized block 1
step synchronized block 1
step synchronized block 1
step synchronized block 1
End synchronized block 1
Start synchronized block 2
step synchronized block 2
step synchronized block 2
step synchronized block 2
step synchronized block 2
step synchronized block 2
step synchronized block 2
End synchronized block 2
main: finished
● 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
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
Obrigado!
linkedin.com/in/paulovsato
Paulo Sato
psato@ciandt.com@impaulosato

Coroutines tech summit

  • 1.
    Coroutines: O que são,como funcionam e como utilizá-las Paulo Sato • Master Engineer • psato@ciandt.com
  • 2.
    Paulo Sato Desenvolvedor Androidhá 10 anos Está com a CI&T há 3 anos Gosto de ensinar pessoas e adoro aprender coisas novas! linkedin.com/in/paulovsato
  • 3.
  • 4.
    Mas afinal, oque é uma Coroutine?
  • 5.
    Coroutine ● São rotinascooperativas, 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
  • 6.
  • 7.
  • 8.
    Suspend Functions ● NoKotlin, 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
  • 9.
    Código Kotlin interface ShowRepository{ @Throws(IOException::class) suspend fun searchShow(query: String): List<ShowInfo> @Throws(IOException::class) suspend fun showRating(id: String): Rating } @Metadata public interface ShowRepository { @Nullable Object searchShow(@NotNull String var1, @NotNull Continuation var2) throws IOException; @Nullable Object showRating(@NotNull String var1, @NotNull Continuation var2) throws IOException; } Código gerado
  • 10.
    Coroutine Context ● Coroutinessempre 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, umtrabalho é 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 CoroutineDispatcherdetermina 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 umanova Coroutine, sem bloquear a thread atual. Retornando um Job, a Coroutine será cancelada quando um Job for cancelado
  • 14.
    Exemplo - Launch valrequest = 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 runin 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 umbloco 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 umaCoroutine 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("Startedmain 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] Startedmain 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.
  • 22.
  • 23.
    Synchronized ● Synchronized nãofunciona em todos os casos para Coroutines
  • 24.
    Exemplo - Synchronized funmain() = runBlocking<Unit> { launch { val result1 = async { dosomething(1) } val result2 = async { dosomething(2) } result1.await() result2.await() println("main: finished") } } @Synchronized suspend fun dosomething(thread : Int){ println("Start synchronized block $thread ") for(i in 0..5){ delay(100) println("step synchronized block $thread") } println("End synchronized block $thread") }
  • 25.
    Resultado Start synchronized block1 Start synchronized block 2 step synchronized block 1 step synchronized block 2 step synchronized block 1 step synchronized block 2 step synchronized block 1 step synchronized block 2 step synchronized block 1 step synchronized block 2 step synchronized block 1 step synchronized block 2 step synchronized block 1 End synchronized block 1 step synchronized block 2 End synchronized block 2 main: finished
  • 26.
    Mutex ● Exclusão mútuapara 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
  • 27.
    Exemplo - Mutex valmutex = Mutex(false) fun main() = runBlocking<Unit> { launch { val result1 = async { dosomething(1) } val result2 = async { dosomething(2) } result1.await() result2.await() println("main: finished") } } suspend fun dosomething(thread : Int){ mutex.lock() println("Start synchronized block $thread ") for(i in 0..5){ delay(100) println("step synchronized block $thread") } println("End synchronized block $thread") mutex.unlock() }
  • 28.
    Resultado Start synchronized block1 step synchronized block 1 step synchronized block 1 step synchronized block 1 step synchronized block 1 step synchronized block 1 step synchronized block 1 End synchronized block 1 Start synchronized block 2 step synchronized block 2 step synchronized block 2 step synchronized block 2 step synchronized block 2 step synchronized block 2 step synchronized block 2 End synchronized block 2 main: finished
  • 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 funmain() = 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
  • 31.