Skip to content

Concurrencia y Programación Asíncrona

La concurrencia y la programación asíncrona son fundamentales para crear aplicaciones eficientes y escalables en un entorno moderno. Scala ofrece herramientas avanzadas, como Futures, Promises, y el modelo de actores, para manejar estas necesidades de manera elegante y segura.


Concurrencia con Future

Un Future representa un cálculo que puede completarse en algún momento en el futuro, ya sea con un resultado exitoso o con un error.

Creación de un Future

scala
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val futuro = Future {
  Thread.sleep(1000) // Simula una operación costosa
  42
}

futuro.foreach(resultado => println(s"Resultado: $resultado"))
  • ExecutionContext: Proporciona el contexto donde se ejecutan los Future. Usualmente es un pool de hilos.
  • foreach: Maneja el valor completado de un Future.

Combinación de Futures

Puedes combinar múltiples Future para manejar dependencias entre operaciones concurrentes.

Usando flatMap y map

scala
val futuro1 = Future { 10 }
val futuro2 = Future { 20 }

val suma = for {
  x <- futuro1
  y <- futuro2
} yield x + y

suma.foreach(resultado => println(s"Suma: $resultado"))

Manejo de Errores

Usa recover o recoverWith para manejar fallos en un Future.

scala
val fallo = Future {
  throw new RuntimeException("Error")
}

fallo.recover {
  case e: RuntimeException => println(s"Recuperado de: ${e.getMessage}")
}

Promise

Un Promise es un contenedor que permite completar manualmente un Future asociado.

Ejemplo de Promise

scala
import scala.concurrent.Promise

val promesa = Promise[Int]()
val futuro = promesa.future

futuro.foreach(valor => println(s"Promesa cumplida con: $valor"))

promesa.success(42) // Completa el Future asociado

Modelo de Actores con Akka

Akka es una biblioteca que implementa el modelo de actores, una forma de gestionar la concurrencia mediante el envío de mensajes entre entidades aisladas.

Configuración Básica

scala
import akka.actor.{Actor, ActorSystem, Props}

class Saludador extends Actor {
  def receive: Receive = {
    case "hola" => println("¡Hola!")
    case _      => println("No entiendo el mensaje.")
  }
}

val sistema = ActorSystem("SistemaEjemplo")
val saludador = sistema.actorOf(Props[Saludador], "saludador")

saludador ! "hola"
saludador ! "adiós"
  • ActorSystem: Administra y supervisa actores.
  • Props: Define cómo se crean los actores.
  • !: Envía un mensaje asincrónico a un actor.

Programación Asíncrona con Await

Puedes usar Await para bloquear un hilo hasta que un Future se complete (aunque esto no es recomendado para programación asíncrona real).

Ejemplo:

scala
import scala.concurrent.Await
import scala.concurrent.duration._

val futuro = Future { 42 }
val resultado = Await.result(futuro, 2.seconds)
println(s"Resultado: $resultado")

Ejemplo Completo: Descarga Concurrente de Datos

scala
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val urls = List("url1", "url2", "url3")

val descargas = urls.map(url => Future {
  println(s"Descargando $url")
  Thread.sleep(1000) // Simula tiempo de descarga
  s"Datos de $url"
})

val todasLasDescargas = Future.sequence(descargas)

todasLasDescargas.foreach(datos => datos.foreach(println))

Buenas Prácticas

  1. Evita bloqueos: Usa map, flatMap, y callbacks en lugar de Await para evitar bloqueos innecesarios.
  2. Gestiona errores: Siempre maneja errores en Future y actores.
  3. Escalabilidad: Usa Akka para aplicaciones complejas con alta concurrencia.
  4. Contexto de ejecución: Define un ExecutionContext apropiado para la carga esperada.
  5. Composición: Usa combinadores como Future.sequence y Future.traverse para manejar múltiples tareas.

Conclusión

Scala proporciona herramientas potentes y flexibles para manejar concurrencia y programación asíncrona. El uso adecuado de Futures, Promises, y el modelo de actores permite escribir aplicaciones eficientes y escalables, maximizando el rendimiento en entornos concurrentes.