⏸️ Funciones suspend
📖 ¿Qué es suspend?
Las funciones suspend son funciones especiales que pueden suspender una corrutina. Son eficientes porque pueden esperar sin bloquear el hilo.
💡 Conceptos Básicos
Palabra clave suspend
// Una función suspend puede llamar a otras funciones suspend
suspend fun doSomething() {
delay(1000) // función suspend
println("¡Completado!")
}
fun main() = runBlocking {
doSomething() // Se llama dentro de una corrutina
}
Función normal vs función suspend
// ❌ No se puede llamar a funciones suspend desde funciones normales
fun normalFunction() {
// delay(1000) // ¡Error de compilación!
}
// ✅ Es posible en funciones suspend
suspend fun suspendFunction() {
delay(1000) // ¡OK!
}
🎯 Ejemplos Prácticos
Petición de Red
data class User(val id: String, val name: String)
suspend fun fetchUser(userId: String): User {
delay(1000) // Simulación de latencia de red
return User(userId, "Juan Pérez")
}
fun main() = runBlocking {
println("Iniciando petición")
val user = fetchUser("user123")
println("Usuario recibido: ${user.name}")
}
Ejecución Secuencial
suspend fun prepareFood(): String {
delay(1000)
return "Comida"
}
suspend fun setTable(): String {
delay(500)
return "Mesa"
}
suspend fun prepareMeal() {
println("Iniciando preparación")
val food = prepareFood()
println("$food lista")
val table = setTable()
println("$table lista")
println("¡Toda la preparación terminada!")
}
fun main() = runBlocking {
val time = measureTimeMillis {
prepareMeal()
}
println("Tiempo transcurrido: ${time}ms") // ~1500ms
}
Ejecución Paralela
suspend fun prepareMealParallel() = coroutineScope {
println("Iniciando preparación")
val foodDeferred = async { prepareFood() }
val tableDeferred = async { setTable() }
val food = foodDeferred.await()
val table = tableDeferred.await()
println("$food, $table listos")
}
fun main() = runBlocking {
val time = measureTimeMillis {
prepareMealParallel()
}
println("Tiempo transcurrido: ${time}ms") // ~1000ms (¡paralelo!)
}
🔧 withContext
Cambio de Dispatcher
suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// Se ejecuta en el hilo IO
delay(1000)
"Datos"
}
}
fun main() = runBlocking {
println("Hilo principal: ${Thread.currentThread().name}")
val data = loadData()
println("De vuelta al principal: ${Thread.currentThread().name}")
println("Datos: $data")
}
Múltiples Contextos
suspend fun processData() {
// Operación IO
val data = withContext(Dispatchers.IO) {
readFromFile()
}
// Operación intensiva de CPU
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}
// Hilo principal (actualización de UI, etc.)
withContext(Dispatchers.Main) {
updateUI(processed)
}
}
suspend fun readFromFile(): String {
delay(500)
return "Contenido del archivo"
}
suspend fun heavyComputation(data: String): String {
delay(1000)
return data.uppercase()
}
suspend fun updateUI(data: String) {
println("Actualización de UI: $data")
}
🎨 Patrones Avanzados
Espera Condicional
suspend fun fetchDataIfNeeded(cache: String?): String {
return cache ?: fetchFromNetwork()
}
suspend fun fetchFromNetwork(): String {
delay(1000)
return "Datos nuevos"
}
fun main() = runBlocking {
// Con caché - retorno inmediato
val cached = fetchDataIfNeeded("Datos en caché")
println(cached)
// Sin caché - petición de red
val fresh = fetchDataIfNeeded(null)
println(fresh)
}
Encadenamiento
suspend fun step1(): Int {
delay(500)
return 10
}
suspend fun step2(input: Int): Int {
delay(500)
return input * 2
}
suspend fun step3(input: Int): Int {
delay(500)
return input + 5
}
suspend fun processChain(): Int {
val a = step1()
val b = step2(a)
val c = step3(b)
return c
}
fun main() = runBlocking {
val result = processChain()
println("Resultado final: $result") // 25 (10 * 2 + 5)
}
Manejo de Errores
suspend fun riskyOperation(): String {
delay(1000)
throw Exception("¡Error de red!")
}
suspend fun safeOperation(): String? {
return try {
riskyOperation()
} catch (e: Exception) {
println("Error ocurrido: ${e.message}")
null
}
}
fun main() = runBlocking {
val result = safeOperation()
println("Resultado: ${result ?: "Fallido"}")
}
🔥 Funciones Prácticas
Manejo de Timeout
suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // Tarda demasiado
"Datos"
}
} catch (e: TimeoutCancellationException) {
println("¡Tiempo agotado!")
null
}
}
fun main() = runBlocking {
val result = fetchWithTimeout()
println("Resultado: ${result ?: "Timeout"}")
}
Lógica de Reintento
suspend fun <T> retry(
times: Int = 3,
initialDelay: Long = 100,
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Intento ${attempt + 1} fallido: ${e.message}")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
return block() // Último intento
}
var attemptCount = 0
suspend fun unstableAPI(): String {
attemptCount++
if (attemptCount < 3) {
throw Exception("Error temporal")
}
return "¡Éxito!"
}
fun main() = runBlocking {
val result = retry(times = 5) {
unstableAPI()
}
println(result)
}
Patrón de Caché
class DataRepository {
private var cache: String? = null
suspend fun getData(forceRefresh: Boolean = false): String {
if (!forceRefresh && cache != null) {
println("Retornando desde caché")
return cache!!
}
println("Obteniendo desde red...")
delay(1000)
val data = "Datos nuevos"
cache = data
return data
}
}
fun main() = runBlocking {
val repo = DataRepository()
// Primera llamada - red
println(repo.getData())
// Segunda llamada - caché
println(repo.getData())
// Actualización forzada
println(repo.getData(forceRefresh = true))
}
🛡️ Funciones Cancelables
Verificación de Cancelación
suspend fun longRunningTask() {
repeat(10) { i ->
if (!isActive) {
println("¡Cancelado!")
return
}
println("Trabajo $i")
delay(500)
}
}
fun main() = runBlocking {
val job = launch {
longRunningTask()
}
delay(2000)
println("¡Cancelando trabajo!")
job.cancelAndJoin()
}
ensureActive
suspend fun heavyWork() {
repeat(10) { i ->
ensureActive() // Lanza excepción si está cancelado
println("Trabajo pesado $i")
Thread.sleep(500) // Simulación de trabajo de CPU
}
}
fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("El trabajo ha sido cancelado")
}
}
delay(2000)
job.cancel()
}
🤔 Preguntas Frecuentes
P1. ¿Las funciones suspend crean hilos?
R: ¡No! suspend solo suspende sin bloquear el hilo.
suspend fun example() {
println("Inicio: ${Thread.currentThread().name}")
delay(1000)
println("Fin: ${Thread.currentThread().name}")
}
// ¡Puede ejecutarse en el mismo hilo!
P2. ¿Y si hago todas las funciones suspend?
R: ¡Es innecesario! Úselo solo cuando haya operaciones asíncronas.
// ❌ Innecesario
suspend fun add(a: Int, b: Int) = a + b
// ✅ Necesario
suspend fun fetchData() = withContext(Dispatchers.IO) {
// Petición de red
}
P3. ¿Puedo llamar funciones normales desde funciones suspend?
R: ¡Por supuesto!
fun normalFunction() = println("Función normal")
suspend fun suspendFunction() {
normalFunction() // ¡OK!
delay(1000)
}
🎬 Conclusión
¡Código asíncrono eficiente con funciones suspend!
Resumen clave:
✅ Suspensión posible con suspend
✅ Espera sin bloquear hilos
✅ Cambio de hilos con withContext
✅ Control de ejecución secuencial/paralela
✅ Trabajos cancelables
Siguiente paso: ¡Aprenda sobre flujos de datos asíncronos en Flow!