Elixir: Task
Elixir y Task para ser fuerte con la concurrencia al construir Apps. Hablamos un poco de como se utiliza, que aspectos nos parecen importantes y como con algo tan fácil de usar como Task tus Apps pueden ser mejores.
Luke: «Mover piedras es una cosa, esto es totalmente diferente.»
Yoda: «No, no es diferente, solo es diferente en tú mente.»
Bueno, henos aquí de nuevo y como saben aquí nos gusta hacer referencia a Star Wars cada que les enseñamos algo de Elixir. Hoy hablaremos de algo tan poderoso como el entrenamiento que le dio Yoda al querido Luke para volverse uno con la fuerza. La concurrencia es cosa que a menudo viene bien en cualquier proyecto. En los Softwares actuales muchas veces resulta necesaria para brindar resultados óptimos; Por eso hoy toca Task pero primero ¿Qué es la concurrencia?
La concurrencia es la capacidad de un sistema para ejecutar múltiples tareas o procesos al mismo tiempo, mejorando la eficiencia y el rendimiento al permitir que diferentes operaciones se superpongan en el tiempo, en lugar de ejecutarse secuencialmente una tras otra.
Task es el módulo de elixir que proporciona herramientas para la gestion de estos procesos. Permite crear y gestionar procesos asincrónicos que son ejecutados en paralelo. Si pensará en un ejemplo probablemente mencionaría un software que crea boletos para compra de eventos, estos boletos se mandaban a crear de manera asincrónica ya que podían llegar a ser miles y la creación del evento no debería esperar a la terminación de una tarea tan larga.
¿Cómo usamos Task?
Uno de los usos mas comunes de Task es convertir código secuencial en código concurrente usando Task.async/1 mientras este proceso es iniciado async notifica al llamado. Task.Async es una técnica vital que permite ejecutar tareas concurrentes sin complicaciones. Piensa en Task.async como esa herramienta que permite saltar de una tarea a otra, gestionando multiples procesos de manera eficiente.
defmodule Force do
def lift_x_wing() do
task = Task.async(fn -> luke_use_the_force() end)
IO.puts("Task creada con PID: #{inspect(task.pid)}")
Task.await(task)
end
def luke_use_the_force() do
# Luke usa la fuerza para levantar el xwing pero el no es tan fuerte
# por lo que este proceso sera un poco tardado.
# Usamos Process.sleep para que esta funcion tarde 20 segundos.
Process.sleep(2000)
end
end
# Hacemos uso del modulo que acabamos de hacer.
result = Force.lift_x_wing()
IO.puts("Resultado: #{result}")
En este ejemplo, Task.async
se utiliza para crear una tarea que hace una pausa. La tarea se ejecuta en paralelo y Task.await
se usa para esperar el resultado de la tarea.
Esperando Resultados con Task.await
Task.await
es una función clave para recuperar el resultado de una tarea asincrónica. Espera a que la tarea termine y devuelve su resultado. Si la tarea no finaliza en el tiempo especificado (en milisegundos), se genera una excepción.
result = Task.await(task, 20_000) # Espera hasta 20 segundos
Es importante manejar adecuadamente las excepciones para evitar que el programa se bloquee si una tarea tarda demasiado en completarse.
Ejecutando Múltiples Tareas Concurrentemente
Uno de los mayores beneficios de Task es la capacidad de ejecutar múltiples tareas en paralelo y luego recolectar sus resultados. Esto se logra combinando Task.async
y Task.await
.
defmodule Training do
def run_tasks do
tasks = [
Task.async(fn -> training_the_force(1) end),
Task.async(fn -> training_the_force(2) end),
Task.async(fn -> training_the_force(3) end)
]
results = tasks |> Enum.map(&Task.await(&1, 5000))
IO.inspect(results)
end
defp training_the_force(n) do
:timer.sleep(2000) # Simulamos un entrenammiento
"Resultado #{n}"
end
end
Training.run_tasks()
En este ejemplo, Task.async
se utiliza para lanzar tres tareas pesadas en paralelo. Luego, Enum.map
y Task.await
se combinan para esperar y recolectar los resultados de todas las tareas.
Monitoreando Procesos con Task
Elixir permite monitorear procesos utilizando Process.monitor
, y Task no es una excepción. Esto es útil para supervisar el estado de las tareas y manejar casos en los que una tarea falle o termine inesperadamente, no necesariamente tienes que traer a un alienigena verde colgado en la espalda supervisando cada proceso (referencia a Yoda entrenando a Luke desde su espalda).
defmodule MasterYoda do
def monitor_tasks do
task = Task.async(fn -> risky_operation() end)
ref = Process.monitor(task.pid)
receive do
{:DOWN, ^ref, :process, _pid, reason} ->
IO.puts("La tarea falló con razón: #{inspect(reason)}")
after
5000 ->
IO.puts("La tarea sigue en ejecución.")
end
end
defp risky_operation do
raise "Algo salió mal"
end
end
MasterYoda.monitor_tasks()
En este ejemplo, Process.monitor
se utiliza para supervisar una tarea que podría fallar. Si la tarea termina inesperadamente, se envía un mensaje :DOWN
que se recibe y maneja adecuadamente.
Task.Supervisor: Supervisando Tareas
Para escenarios más complejos y robustos, Elixir proporciona Task.Supervisor
, un módulo que facilita la supervisión y gestión de tareas. Esto es particularmente útil para aplicaciones que necesitan ejecutar y monitorear un gran número de tareas.
defmodule JediCouncil do
def start_supervised_tasks do
{:ok, supervisor} = Task.Supervisor.start_link(name: MyTaskSupervisor)
Task.Supervisor.async_nolink(supervisor, fn -> war_defenses(1) end)
Task.Supervisor.async_nolink(supervisor, fn -> war_defenses(2) end)
end
defp war_defenses(n) do
:timer.sleep(3000)
IO.puts("Guerra #{n} completada.")
end
end
JediCouncil.start_supervised_tasks()
En este ejemplo, Task.Supervisor
se utiliza para crear y supervisar dos tareas largas. async_nolink
lanza las tareas sin vincularlas al proceso actual, permitiendo que sigan ejecutándose incluso si el proceso principal termina. Pensemos en este modulo como el consejo Jedi supervisando la guerra de los clones (¡ya sé! perdieron la guerra, pero pues si la supervisaron).
Conclusión
Task es una herramienta muy fuerte dentro de los módulos de elixir, flexible y manejable donde las aplicaciones son beneficiadas por la concurrencia. Ese paralelismo que nos permite supervisar y gestionar estos procesos brindan capacidades necesarias a la hora de construir aplicaciones eficientes y escalábles.
Al dominar Task, puedes aprovechar al máximo la naturaleza concurrente de Elixir, lo digo de nuevo piensa en Luke. Antes de ir con Yoda su uso de la fuerza era limitado, despues de Yoda (Task) su uso en la fuerza era fuerte, lleno de confianza, sé como Luke y aprende a dominar Task permitiendo que tu código sea tan ágil y poderoso como un el del Jedi más legendario del universo de Star Wars.
¡Que la Fuerza de la concurrencia esté contigo!
Referencias:
Y recuerda, siempre puedes visitarnos para que hagamos tus Apps en: