Pro Yearly is on sale from $80 to $50! »

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

F0d5badbf3489cf94bcea112ce9f3ed6?s=128

Antonio Leiva

June 25, 2020
Tweet

Transcript

  1. Introducción a las corrutinas Actualizadas a Kotlin 1.3 Antonio Leiva

    Kotlin GDE | Partner JetBrains
  2. None
  3. None
  4. 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 }
  5. 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 } } }
  6. 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
  7. 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.
  8. 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 }
  9. 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
  10. 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 }
  11. 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 }
  12. 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 }
  13. 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?
  14. 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 }
  15. 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)
  16. 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
  17. 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
  18. 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?
  19. ¿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 }
  20. withContext suspend fun suspendLogin(username: String, password: String) = withContext(Dispatchers.Main) {

    userService.doLogin(username, password) }
  21. withContext Esto bloquearía porque estoy usando el Main Dispatcher suspend

    fun suspendLogin(username: String, password: String) = withContext(Dispatchers.Main) { userService.doLogin(username, password) }
  22. withContext ¡SOLUCIONADO! suspend fun suspendLogin(username: String, password: String) = withContext(Dispatchers.IO)

    { userService.doLogin(username, password) }
  23. 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) }
  24. 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
  25. coroutine(Dispatchers.Main) { ... }

  26. 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
  27. 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
  28. 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
  29. GlobalScope.launch(Dispatchers.Main) { ... }

  30. 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.
  31. val job = GlobalScope.launch(Dispatchers.Main) { doCoroutineTask() val res1 = suspendingTask1()

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

    val res2 = suspendingTask2() process(res1, res2) } job.cancel() job.cancel()
  33. 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
  34. 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
  35. 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
  36. 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)
  37. 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
  38. Scopes ≻ Dos opciones ◦ GlobalScope: usado por las corrutinas

    que pueden ejecutarse durante todo el tiempo de vida de la App ◦ Extender CoroutineScope
  39. Scopes - Global scope GlobalScope.launch(Dispatchers.Main) { ... }

  40. Scopes - extender CoroutineScope class MainActivity : AppCompatActivity(), CoroutineScope {

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

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

    password: String): User = suspendCancellableCoroutine { continuation -> userService.doLoginAsync(username, password) { user -> continuation.resume(user) } }
  43. ¿Quieres saber más? youtube.com/devexperto