Programación asíncrona en Kotlin: guía completa en la práctica
Las aplicaciones modernas requieren alto rendimiento y capacidad de respuesta. La programación asíncrona en Kotlin con corrutinas (coroutines) se ha convertido en el estándar de facto para trabajar con solicitudes de red, bases de datos y operaciones de archivos. En esta guía, analizaremos los conceptos principales, mostraremos ejemplos prácticos y proporcionaremos plantillas listas para usar en sus proyectos.
1. Fundamentos de las corrutinas en Kotlin
Las corrutinas son hilos ligeros que permiten pausar la ejecución de una función sin bloquear el hilo principal. A diferencia de los hilos tradicionales, las corrutinas pueden pausarse en cualquier momento y reanudarse más tarde, lo que las hace ideales para operaciones asíncronas.
1.1. Agregar dependencias
Para comenzar, agregue las dependencias en build.gradle.kts:
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") // para Android}1.2. Primera corrutina
Creemos una corrutina simple usando runBlocking, un puente entre el mundo síncrono y asíncrono:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // retardo no bloqueante println("¡Mundo!") } println("Hola,") delay(2000L) // esperamos a que termine la corrutina}En este ejemplo, launch inicia una nueva corrutina, y delay es una función suspend que no bloquea el hilo.
2. Funciones suspend y paralelismo estructurado
2.1. Creación de funciones suspend
Cualquier función que pueda pausarse debe marcarse con el modificador suspend. Estas funciones solo pueden llamarse desde corrutinas u otras funciones suspend:
suspend fun fetchUserData(userId: String): User { delay(2000L) // simulación de solicitud de red return User(userId, "Iván Petrov")}
suspend fun fetchUserPosts(userId: String): List<Post> { delay(1500L) return listOf(Post("Título 1"), Post("Título 2"))}2.2. Ejecución secuencial
Por defecto, las corrutinas se ejecutan secuencialmente dentro de un mismo launch:
fun main() = runBlocking { val time = measureTimeMillis { val user = fetchUserData("123") val posts = fetchUserPosts("123") println("${user.name} tiene ${posts.size} publicaciones") } println("Tiempo de ejecución: $time ms") // ~3500 ms}2.3. Ejecución paralela con async/await
Para ejecución paralela, use async y await:
fun main() = runBlocking { val time = measureTimeMillis { val user = async { fetchUserData("123") } val posts = async { fetchUserPosts("123") } println("${user.await().name} tiene ${posts.await().size} publicaciones") } println("Tiempo de ejecución: $time ms") // ~2000 ms (en paralelo)}3. Dispachers de corrutinas y contextos
Los dispachers determinan en qué hilo o grupo de hilos se ejecutará la corrutina. Los dispachers principales son:
- Dispatchers.Default — para tareas intensivas en CPU (por defecto)
- Dispatchers.IO — para operaciones de entrada/salida (red, archivos)
- Dispatchers.Main — para el hilo de UI (Android, Swing)
- Dispatchers.Unconfined — no está vinculado a un hilo específico
3.1. Ejemplo con Dispatchers.IO
fun main() = runBlocking { launch(Dispatchers.IO) { // Solicitud de red val response = httpClient.get("https://api.example.com/data") withContext(Di