Slide 1

Slide 1 text

Introducción a las corrutinas Actualizadas a Kotlin 1.3 Antonio Leiva Kotlin GDE | Partner JetBrains

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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 }

Slide 5

Slide 5 text

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 } } }

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

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 }

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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 }

Slide 11

Slide 11 text

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 }

Slide 12

Slide 12 text

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 }

Slide 13

Slide 13 text

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?

Slide 14

Slide 14 text

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 }

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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?

Slide 19

Slide 19 text

¿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 }

Slide 20

Slide 20 text

withContext suspend fun suspendLogin(username: String, password: String) = withContext(Dispatchers.Main) { userService.doLogin(username, password) }

Slide 21

Slide 21 text

withContext Esto bloquearía porque estoy usando el Main Dispatcher suspend fun suspendLogin(username: String, password: String) = withContext(Dispatchers.Main) { userService.doLogin(username, password) }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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) }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

coroutine(Dispatchers.Main) { ... }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

GlobalScope.launch(Dispatchers.Main) { ... }

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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)

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Scopes ≻ Dos opciones ○ GlobalScope: usado por las corrutinas que pueden ejecutarse durante todo el tiempo de vida de la App ○ Extender CoroutineScope

Slide 39

Slide 39 text

Scopes - Global scope GlobalScope.launch(Dispatchers.Main) { ... }

Slide 40

Slide 40 text

Scopes - extender CoroutineScope class MainActivity : AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job private lateinit var job: Job }

Slide 41

Slide 41 text

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) job = Job() ... } override fun onDestroy() { job.cancel() super.onDestroy() } Scopes - extender CoroutineScope

Slide 42

Slide 42 text

Extra - Convertir Callback a Corrutinas suspend fun suspendAsyncLogin(username: String, password: String): User = suspendCancellableCoroutine { continuation -> userService.doLoginAsync(username, password) { user -> continuation.resume(user) } }

Slide 43

Slide 43 text

¿Quieres saber más? youtube.com/devexperto