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

Kotlin Nights - Introducción a las corrutinas

Kotlin Nights - Introducción a las corrutinas

Link a la grabación en YouTube: https://youtu.be/cGlaIIT5PhM?t=2094

Antonio Leiva

June 25, 2020
Tweet

More Decks by Antonio Leiva

Other Decks in Technology

Transcript

  1. progress.visibility = View.VISIBLE userService.doLoginAsync(username, password) { user -> userService.requestCurrentFriendsAsync(user) {

    friends -> val finalUser = user.copy(friends = friends) toast("User ${finalUser.name} has ${finalUser.friends.size} friends") progress.visibility = View.GONE }
  2. progress.visibility = View.VISIBLE userService.doLoginAsync(username, password) { user -> userService.requestCurrentFriendsAsync(user) {

    currentFriends -> userService.requestSuggestedFriendsAsync(user) { suggestedFriends -> val finalUser = user.copy(friends = currentFriends + suggestedFriends) toast("User ${finalUser.name} has ${finalUser.friends.size} friends") progress.visibility = View.GONE } } }
  3. Corrutinas - Como los hilos, pero mejor ≻ Código asíncrono

    escrito de forma secuencial ≻ Varias corrutinas - 1 hilo ◦ El número de hilos concurrentes es muy limitado ◦ El número de corrutinas concurrentes casi infinito
  4. Corrutinas ≻ Basadas en la idea de funciones de suspensión

    ◦ Pueden suspender la ejecución ◦ Devuelven la ejecución a la corrutina cuando terminan ≻ Las corrutinas son el lugar seguro donde las funciones de suspensión (normalmente) no bloquearán el hilo actual.
  5. coroutine { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin(username, password) } val currentFriends = suspended { userService.requestCurrentFriends(user) } val finalUser = user.copy(friends = currentFriends) toast("User ${finalUser.name} has ${finalUser.friends.size} friends") progress.visibility = View.GONE }
  6. coroutine { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin(username, password) } val currentFriends = suspended { userService.requestCurrentFriends(user) } val finalUser = user.copy(friends = currentFriends) toast("User ${finalUser.name} has ${finalUser.friends.size} friends") progress.visibility = View.GONE } Suspending functions
  7. Funciones de suspensión ≻ Bloquean la ejecución de la corrutina

    ≻ Se ejecutan en un hilo específico (podemos configurar cuál) suspend fun suspendingFunction() : Int { // Long running task return 0 }
  8. coroutine { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE }
  9. Se ejecutará en el hilo principal? coroutine { progress.visibility =

    View.VISIBLE val user = suspended { userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE }
  10. coroutine { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE } DEPENDE Se ejecutará en el hilo principal?
  11. coroutine(CoroutineContext) { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE }
  12. CoroutineContext ≻ Contexto de ejecución específico para la corrutina ◦

    Dispatcher: especifica los hilos donde la corrutina se puede ejecutar ≻ El dispatcher se puede proveer ◦ Explícitamente ◦ Mediante un Scope de Corrutina (lo vemos después)
  13. coroutine(Dispatchers.Main) { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE } Main Thread
  14. coroutine(Dispatchers.Main) { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE } Main Thread
  15. coroutine(Dispatchers.Main) { progress.visibility = View.VISIBLE val user = suspended {

    userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE } ¿Y qué ocurre con la función de suspensión?
  16. ¿Y qué ocurre con la función de suspensión? ¡TAMBIÉN DEPENDE!

    coroutine(Dispatchers.Main) { progress.visibility = View.VISIBLE val user = suspended { userService.doLogin... val currentFriends = suspended { userServi... val finalUser = user.copy(friends = curren... toast("User ${finalUser.name} has ${finalU... progress.visibility = View.GONE }
  17. withContext Esto bloquearía porque estoy usando el Main Dispatcher suspend

    fun suspendLogin(username: String, password: String) = withContext(Dispatchers.Main) { userService.doLogin(username, password) }
  18. withContext ≻ Puedes evitar crear funciones de suspensión val user

    = withContext(Dispatchers.IO) { userService.doLogin(username, password) } val currentFriends = withContext(Dispatchers.IO) { userService.requestCurrentFriends(user) }
  19. Dispatchers ≻ Default: Para tareas de CPU intensivas → Procesamiento

    ≻ IO: Para operaciones entrada-salida: base datos, red, etc ≻ Unconfined: usar con cuidado ≻ Main: Específico de Android - hilo principal
  20. runBlocking ≻ Bloquea el hilo principal para ejecutar las corrutinas

    ≻ Casi nunca debería ser usado. Solo... ◦ Para testing, para llamar y testear funciones de suspensión
  21. runBlocking fun testSuspendingFunction() = runBlocking { val res = suspendingTask1()

    assertEquals(0, res) } ≻ Bloquea el hilo principal para ejecutar las corrutinas ≻ Casi nunca debería ser usado. Solo... ◦ Para testing, para llamar y testear funciones de suspensión
  22. launch ≻ No bloquea el hilo principal (si usamos los

    dispatchers adecuados) ≻ Builder básico → devuelve un Job ≻ Necesita un Scope (luego lo vemos) - GlobalScope por ahora
  23. jobs ≻ Objeto devuelvo por una corrutina ≻ Permite ◦

    Cancelar la corrutina ◦ Esperar a que la corrutina termine ≻ Puede tener jobs padres. Si un job padre es cancelado, los hijos se cancelan también.
  24. val job = GlobalScope.launch(Dispatchers.Main) { doCoroutineTask() val res1 = suspendingTask1()

    val res2 = suspendingTask2() process(res1, res2) } job.join() job.join()
  25. val job = GlobalScope.launch(Dispatchers.Main) { doCoroutineTask() val res1 = suspendingTask1()

    val res2 = suspendingTask2() process(res1, res2) } job.cancel() job.cancel()
  26. async ≻ Necesita ser llamado desde una corrutina ≻ La

    llamada no bloquea la corrutina superior, sino que empieza a ejecutarse en segundo plano ◦ Esto puede modificarse usando el argumento start ≻ Devuelve un objeto deferred Deferred (que extiende Job) ≻ Cuando se llama await, entonces bloquea
  27. GlobalScope.launch(Dispatchers.Main) { val user = withContext(Dispatchers.IO) { userService.doLogin(username, password) }

    val currentFriends = withContext(Dispatchers.IO) { userService.requestCurrentFriends(user) } val suggestedFriends = withContext(Dispatchers.IO) { userService.requestSuggestedFriends(user) } val finalUser = user.copy(friends = currentFriends + suggestedFriends) } Using withContext: 6 secs
  28. GlobalScope.launch(Dispatchers.Main) { val user = withContext(Dispatchers.IO) { userService.doLogin(username, password) }

    val currentFriends = async(Dispatchers.IO) { userService.requestCurrentFriends(user) } val suggestedFriends = async(Dispatchers.IO) { userService.requestSuggestedFriends(user) } val finalUser = user.copy(friends = currentFriends.await() + suggestedFriends.await()) } Using async: 4 secs
  29. async ≻ La llamada no bloquea la corrutina superior, sino

    que empieza a ejecutarse en segundo plano ◦ Esto puede modificarse usando el argumento start async(Dispatchers.IO, CoroutineStart.LAZY)
  30. Scopes ≻ Limitan el el ámbito de ejecución de las

    corrutinas ≻ Ayuda a cancelar todas las corrutinas cuando el scope ya no está disponible. ≻ Una Activity es el mejor ejemplo
  31. Scopes ≻ Dos opciones ◦ GlobalScope: usado por las corrutinas

    que pueden ejecutarse durante todo el tiempo de vida de la App ◦ Extender CoroutineScope
  32. Scopes - extender CoroutineScope class MainActivity : AppCompatActivity(), CoroutineScope {

    override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job private lateinit var job: Job }
  33. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() ...

    } override fun onDestroy() { job.cancel() super.onDestroy() } Scopes - extender CoroutineScope
  34. Extra - Convertir Callback a Corrutinas suspend fun suspendAsyncLogin(username: String,

    password: String): User = suspendCancellableCoroutine { continuation -> userService.doLoginAsync(username, password) { user -> continuation.resume(user) } }