Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coroutines - Kotlin everywhere

Coroutines - Kotlin everywhere

Palestra que fala sobre o que é uma coroutine, como elas funcionam no Kotlin e como ela pode ser utilizada

Avatar for Paulo Sato

Paulo Sato

August 31, 2019

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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)
  8. Launch • Lança uma nova Coroutine, sem bloquear a thread

    atual. Retornando um Job, a Coroutine será cancelada quando um Job for cancelado
  9. Exemplo - Launch val request = launch { // it

    spawns two other jobs, one with GlobalScope GlobalScope.launch { println("job1: I run in GlobalScope and execute independently!") delay(1000) println("job1: I am not affected by cancellation of the request") } // and the other inherits the parent context launch { 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") } } 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?")
  10. 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?
  11. 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
  12. Exemplo - withContext parentJob = CoroutineScope(Dispatchers.Main + exceptionHandler).launch { val

    result = withContext(Dispatchers.IO) { executeOnBackground(request) } response(result) }
  13. 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
  14. Exemplo - Async log("Started main coroutine") // run two background

    value computations val v1 = async(CoroutineName("v1coroutine")) { delay(500) log("Computing v1") 252 } val v2 = async(CoroutineName("v2coroutine")) { delay(1000) log("Computing v2") 6 } log("The answer for v1 / v2 = ${v1.await() / v2.await()}")
  15. 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
  16. 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") }
  17. 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
  18. 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
  19. 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() }
  20. 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
  21. • 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
  22. Exemplo - 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 println("Done!") // we are done coroutineContext.cancelChildren() // cancel children coroutines } fun CoroutineScope.produceNumbers() = produce<Int> { var x = 1 while (true) send(x++) // infinite stream of integers starting from 1 } fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce { for (x in numbers) send(x * x) } 1 4 9 16 25 Done!