Saltar al contenido principal

🎯 Fundamentos de Corrutinas

📖 Comenzando con Corrutinas

Para usar corrutinas, primero necesita conocer los constructores de corrutinas. ¡Aprendamos sobre los tres constructores más básicos!

💡 runBlocking

Uso Básico

import kotlinx.coroutines.*

fun main() = runBlocking { // ¡Inicia la corrutina!
println("Hello")
delay(1000) // Espera 1 segundo
println("World!")
}

Características

fun main() {
println("Before")

runBlocking {
delay(1000)
println("Inside coroutine")
}

println("After") // Espera hasta que la corrutina termine
}
// Before
// (espera 1 segundo)
// Inside coroutine
// After

Atención: ¡runBlocking bloquea el hilo actual! Úselo principalmente solo en pruebas o en la función main.

🚀 launch

Iniciando Tareas Asíncronas

fun main() = runBlocking {
launch {
delay(1000)
println("Task 1")
}

launch {
delay(500)
println("Task 2")
}

println("Main")
}
// Main
// (después de 500ms) Task 2
// (después de 1000ms) Task 1

Devuelve Job

fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("Working... $i")
delay(500)
}
}

delay(1300)
println("¡Cancelando tarea!")
job.cancel() // Cancela la tarea
job.join() // Espera a que se complete la cancelación
}

Gestionando Múltiples Tareas

fun main() = runBlocking {
val jobs = List(5) { i ->
launch {
delay(1000L * i)
println("Tarea $i completada")
}
}

jobs.forEach { it.join() } // Espera todas las tareas
println("¡Todas las tareas terminadas!")
}

⚡ async

Recibiendo Resultados

fun main() = runBlocking {
val deferred = async {
delay(1000)
return@async "valor de resultado"
}

println("Trabajando...")
val result = deferred.await() // Espera el resultado
println("Valor recibido: $result")
}

launch vs async

fun main() = runBlocking {
// launch - sin resultado
launch {
delay(1000)
println("launch completado")
}

// async - devuelve resultado
val result = async {
delay(1000)
"async completado"
}

println(result.await())
}

🎯 Ejemplos Prácticos

Procesamiento Paralelo

import kotlin.system.measureTimeMillis

fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }

println("Resultado: ${one.await()}, ${two.await()}")
}

println("Tiempo transcurrido: ${time}ms") // ~1000ms (ejecución paralela)
}

suspend fun fetchData1(): String {
delay(1000)
return "datos1"
}

suspend fun fetchData2(): String {
delay(1000)
return "datos2"
}

Secuencial vs Paralelo

fun main() = runBlocking {
// ❌ Ejecución secuencial (lenta)
val time1 = measureTimeMillis {
val one = fetchData1()
val two = fetchData2()
println("$one, $two")
}
println("Secuencial: ${time1}ms") // ~2000ms

// ✅ Ejecución paralela (rápida)
val time2 = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }
println("${one.await()}, ${two.await()}")
}
println("Paralelo: ${time2}ms") // ~1000ms
}

Manejo de Excepciones

fun main() = runBlocking {
val deferred = async {
delay(1000)
throw Exception("¡Ocurrió un error!")
}

try {
deferred.await()
} catch (e: Exception) {
println("Error capturado: ${e.message}")
}
}

🔥 Patrones Útiles

Timeout

fun main() = runBlocking {
try {
withTimeout(1300) {
repeat(3) { i ->
println("Tarea $i")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("¡Tiempo agotado!")
}
}

Múltiples Llamadas a API

data class UserData(
val profile: String,
val posts: List<String>,
val friends: List<String>
)

suspend fun loadUserData(userId: String): UserData = coroutineScope {
val profile = async { fetchProfile(userId) }
val posts = async { fetchPosts(userId) }
val friends = async { fetchFriends(userId) }

UserData(
profile.await(),
posts.await(),
friends.await()
)
}

suspend fun fetchProfile(id: String): String {
delay(500)
return "Profile($id)"
}

suspend fun fetchPosts(id: String): List<String> {
delay(800)
return listOf("Post1", "Post2")
}

suspend fun fetchFriends(id: String): List<String> {
delay(600)
return listOf("Friend1", "Friend2")
}

Lógica de Reintento

suspend fun <T> retryIO(
times: Int = 3,
delay: Long = 1000,
block: suspend () -> T
): T {
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Intento ${attempt + 1} fallido")
delay(delay)
}
}
return block() // Último intento
}

fun main() = runBlocking {
val result = retryIO(times = 3) {
fetchDataFromAPI()
}
println(result)
}

var attempt = 0
suspend fun fetchDataFromAPI(): String {
attempt++
if (attempt < 3) {
throw Exception("Error de red")
}
return "¡Éxito!"
}

🛠️ coroutineScope

Concurrencia Estructurada

suspend fun doWork() = coroutineScope {
launch {
delay(1000)
println("Tarea 1")
}

launch {
delay(2000)
println("Tarea 2")
}

println("Todas las tareas iniciadas")
// Espera hasta que todos los hijos terminen
}

fun main() = runBlocking {
doWork()
println("¡Completado!")
}

Propagación de Excepciones

suspend fun riskyWork() = coroutineScope {
launch {
delay(500)
throw Exception("¡Error!")
}

launch {
delay(1000)
println("Este código no se ejecuta")
}
}

fun main() = runBlocking {
try {
riskyWork()
} catch (e: Exception) {
println("Error capturado: ${e.message}")
}
}

🤔 Preguntas Frecuentes

P1. ¿Cuál es la diferencia entre launch y async?

R: ¡Si devuelve resultado o no!

fun main() = runBlocking {
// launch - no necesita resultado
launch {
println("Tarea simple")
}

// async - necesita resultado
val result = async {
"valor a devolver"
}
println(result.await())
}

P2. ¿Cuál es la diferencia entre join() y await()?

R: ¡join espera la finalización, await espera el resultado!

fun main() = runBlocking {
val job = launch {
delay(1000)
}
job.join() // Solo espera la finalización

val deferred = async {
delay(1000)
"resultado"
}
val result = deferred.await() // Espera el resultado
}

P3. ¿Las corrutinas se cancelan automáticamente?

R: ¡Si el padre se cancela, los hijos también se cancelan!

fun main() = runBlocking {
val parent = launch {
val child = launch {
repeat(10) {
println("Tarea hija $it")
delay(500)
}
}

delay(1300)
println("Padre completado")
}

delay(2000)
parent.cancel() // Los hijos también se cancelan
}

🎬 Conclusión

¡Ha dominado los constructores básicos de corrutinas!

Resumen clave:
✅ runBlocking - Para pruebas/función main
✅ launch - Tareas sin resultado necesario
✅ async - Recibir resultados
✅ coroutineScope - Concurrencia estructurada
✅ Mejora del rendimiento con procesamiento paralelo

Próximo paso: ¡Aprenda en profundidad sobre las funciones de suspensión en funciones suspend!