Programação assíncrona
utilizando Coroutines
Diego Gonçalves Santos
Desenvolvedor @
Coroutines
Código Assíncrono
Cenário
Imagine que você está usando a API do TDC e que para usar
os serviços é necessário enviar sua chave e receber os
tokens de autenticação.
Single Thread
fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Callbacks
fun listEvents() {
getTokenAsync { token ->
getEventsAsync(token) { events ->
displayEvents(events)
}
}
}
Callbacks
fun listEvents() {
getTokenAsync { token ->
getEventsAsync(token) { events ->
displayEvents(events)
}
}
}
Callback
Hell!!
RX
R
Exemplo de código usando Rx
fun listEvents() {
requestTokenAsync()
.flatMap { token -> requestEventsAsync(token) }
.subscribe({ events -> displayEvents(events)})
}
RX
Curva de aprendizado íngreme
Coroutines
suspend fun getTokenAsync() {...}
suspend fun getEventsAsync() {...}
suspend fun listEvents() {
val token = getTokenAsync()
val events = getEventsAsync(token)
displayEvents(events)
}
Coroutines
Coroutines permitem que você escreva seu código de forma
sequencial e cria light-weight threads para lidar com o código
assíncrono.
Coroutines
Coroutines precisam de um contexto especial para rodar,
portanto elas só rodam dentro de Coroutine Builders ou
dentro de outras Coroutines
Launch (sequencial)
Coroutines Builders
Launch
launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
Coroutines Builders
Launch
launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
Hello,
World!
Coroutines Builders
Launch - Job Join
val job = launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
Coroutines Builders
Launch - Job Join
val job = launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
Fork Coroutine
Main Thread
Join
Coroutines Builders
Launch - Job Join
val job = launch {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
Hello,
World!
Coroutines Builders
Launch - Job Cancel
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
Coroutines Builders
Launch - Job Cancel
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
Coroutines Builders
Launch - Job Cancel And Join
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
Coroutines Builders
Launch - Quando a Coroutine para de ser processada ?
val job = launch {
var i = 0
while (i <= 5000) {
println("I'm sleeping ${i++} ...")
}
}
delay(1300L)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 …
…
I'm sleeping 5000 …
main: I'm tired of waiting!
main: Now I can quit.
Coroutines Builders
Launch - Garantindo que a Coroutine vai parar quando solicitado
val job = launch {
var i = 0
while (isActive) {
if(i <= 5000) { println("I'm sleeping ${i++} ...")}
}
delay(1000)
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
RunBlocking (Bloqueia
Thread)
Coroutines Builders
Run Blocking
val job = runBlocking {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
Coroutines Builders
Run Blocking
val job = runBlocking {
delay(1000L)
println("World!")
}
println("Hello,")
job.join()
World!
Hello,
Coroutines Builders
Launch - UI Context
suspend fun listEvents() {
launch(UI) {
val token = getTokenAsync()
val events = getEventsAsync(token)
displayEvents(events)
}
}
Async (concorrente)
Coroutines Builders
Launch - Sequencial
launch{
val image1 = loadImage1()
val image2 = loadImage2()
setBackgrounds(image1, image2)
}
Coroutines Builders
Async - Concorrente
val image1 = async { loadImage1() }
val image2 = async { loadImage2() }
setBackgrounds(image1.await(), image2.await())
Codifique sequencialmente sem
receio!
Use loops, high-order functions, etc
for (i in 1..100_000) {
launch {
doSomethingHeavy()
}
}
Coroutines vs Threads
Cenário
Sistema que recebe uma requisição, lê o arquivo local, faz o
upload desse arquivo e retorna a url para acessar esse
arquivo
Single Thread
Multiple Threads
Coroutines - Sub-rotinas
Coroutines - non-preemptive multi-tasking
Usar os recursos de forma
eficiente e multi-tarefas
colaborativa onde as
próprias funções controlam o
fluxo, disponibilizam a
thread e continuam depois de
onde pararam, sem a
intervenção de um SO.
Coroutines - SÃO light-weight threads
val jobs = List(100_000) {
launch {
delay(1000L)
print(".")
}
}
jobs.forEach { it.join() }
Não existe mágica: De
Callbacks para código
sequencial
Direct Style vs
Continuation-Passing
Style
Direct Style
fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Continuation
Continuation-Passing Style
fun listEvents() {
getTokenAsync { token ->
getEventsAsync(token) { events ->
displayEvents(events)
}
}
}
Continuation
Coroutines Direct Style
suspend fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Continuation
Como é possível código
assíncrono sem
callbacks?
Convertendo para CPS
suspend fun listEvents() {...}
Convertendo para CPS
suspend fun listEvents() {...}
Java/JVM callback
Object listEvents(Continuation<Void> cont) {...}
Convertendo para CPS
suspend fun listEvents() {...}
Java/JVM callback
Object listEvents(Continuation<Void> cont) {...}
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Convertendo para CPS
suspend fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Continuation
Inicial
Convertendo para CPS
suspend fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Continuation
Convertendo para CPS
suspend fun listEvents() {
val token = getToken()
val events = getEvents(token)
displayEvents(events)
}
Continuation
Callbacks?
fun listEvents() {
getTokenAsync { token ->
getEventsAsync(token) { events ->
displayEvents(events)
}
}
}
Convertendo para CPS
suspend fun listEvents() {
// Label 0
val token = getToken()
// Label 1
val events = getEvents(token)
// Label 2
displayEvents(events)
}
Convertendo para CPS
suspend fun listEvents(Continuation<Void> cont) {
val sm = object: CoroutineImpl {...}
switch(sm.label) {
case 0:
getToken(sm)
case 1:
getEvents(sm.token, sm)
case 2:
displayEvents(sm.events)
}
}
Convertendo para CPS
suspend fun listEvents(Continuation<Void> cont) {
val sm = object: CoroutineImpl {...}
switch(sm.label) {
case 0:
sm.label = 1
getToken(sm)
case 1:
...
}
}
Convertendo para CPS
suspend fun listEvents(Continuation<Void> cont) {
val sm = object: CoroutineImpl {
fun resume(...) {
listEvents(this)
}
}
switch(sm.label) {
case 0:
...
}
}
Convertendo para CPS
suspend fun listEvents(Continuation<Void> cont) {
val sm = object: CoroutineImpl {...}
switch(sm.label) {
case 0:
...
case 1:
sm.label = 2
getEvents(sm.token, sm)
}
}
Extensions
Extension em Strings
fun String.toNumber(): String {
return NumberFormat.getInstance()
.format(this.toDouble())
}
Extension em Strings
fun String.toNumber(): String {
return NumberFormat.getInstance()
.format(this.toDouble())
}
val numberString = “1”
val number = numberString.toNumber()
Adaptando callbacks
para Coroutines
Chamada convencional Retrofit
requestToken().
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>?, response: Response<T>) {
// Handle response
}
override fun onFailure(call: Call<T>, t: Throwable) {
// Handle error
}
})
Chamada convertida para Coroutine
suspend fun <T : Any?> Call<T>.awaitResponse(): Response<T> {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>?, response: Response<T>) {
if (response.isSuccessful) continuation.resume(response)
else continuation.resumeWithException(Throwable())
}
override fun onFailure(call: Call<T>, t: Throwable) {
if (continuation.isCancelled) return
continuation.resumeWithException(t)
}
})
}
}
Chamada convertida para Coroutine
suspend fun <T : Any?> Call<T>.awaitResponse(): Response<T>
{
...
}
val token = requestToken().awaitResponse()
Libs interessantes usando Coroutines
● Kotlin Coroutines for Retrofit
https://github.com/gildor/kotlin-coroutines-retrofit
● Kotlin Coroutine (Experimental) Adapter
https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter
● Peko
https://github.com/deva666/Peko
● Anko Coroutines
https://github.com/Kotlin/anko/wiki/Anko-Coroutines
● Prefekt
https://github.com/StylingAndroid/Prefekt
Projeto de Exemplo
https://github.com/DiegoGSantos/Coroutines-Example
Referências
KotlinConf 2017 - Introduction to Coroutines by Roman Elizarov
https://www.youtube.com/watch?v=_hfBv0a09Jc&t=34s
KotlinConf 2017 Deep Dive into Coroutines on JVM by Roman Elizarov
https://www.youtube.com/watch?v=YrrUCSi72E8
Github official: https://github.com/Kotlin/kotlinx.coroutines
Android + kotlin coroutines: Async de um jeito "diferente", por Walmyr Carvalho (Android Meetup #55)
https://www.youtube.com/watch?v=Yr2p4gLUwBA
Referências
Artigos
http://blog.alexnesterov.com/post/coroutines/
https://superkotlin.com/coroutines/
https://en.wikipedia.org/wiki/Cooperative_multitasking
https://android.jlelse.eu/a-first-walk-into-kotlin-coroutines-on-android-fe4a6e25f46a
Obrigado!
Diego Gonçalves Santos
DiegoGSantosDiegoGSantos @DiegoGSantos

TDC2018SP | Trilha Kotlin - Programacao assincrona utilizando Coroutines