Saltar al contenido
Programemos.net Decisiones pragmáticas sobre .NET, Azure, arquitectura de software e IA.
PROGRAMACIÓN

Tareas en segundo plano con Hangfire

Hangfire resuelve un problema concreto en .NET: sacar de una petición HTTP el trabajo que no necesita terminar antes de responder. Enviar un correo, procesar un archivo o reintentar una integración son buenos ejemplos. La diferencia frente a lanzar un Task.Run es importante: Hangfire serializa el trabajo y lo guarda en un almacenamiento para que pueda sobrevivir a reinicios y reintentarse.

En este artículo usaré una Minimal API en .NET 8 y almacenamiento en memoria para reducir ruido. Ese almacenamiento es solo para aprender y probar: en producción debes configurar una opción persistente, como SQL Server o Redis, y operar el servidor de Hangfire con la misma disciplina que cualquier otro componente de tu aplicación.

NOTA: En esta publicación se maneja el término «tarea«, para definir un proceso a ejecutar, pero la documentación de Hangfire hace referencia al término, «trabajo», para este artículo es lo mismo concepto.

¿Qué es Hangfire?

Hangfire se presenta en su página oficial como una herramienta que simplifica el procesamiento en segundo plano en aplicaciones .NET y .NET Core de manera sencilla y eficiente. Lo que destaca de inmediato es que no requiere servicios de Windows ni procesos independientes adicionales. Además, cuenta con el respaldo de almacenamiento persistente, brindando estabilidad a las operaciones que gestiona.

En términos simples, Hangfire administra tareas en segundo plano desde que se crean hasta que se ejecutan, incluyendo persistencia, estados, reintentos y visualización. No sustituye una arquitectura de mensajería distribuida en todos los escenarios, pero resulta muy práctico cuando el trabajo pertenece a la misma aplicación .NET.

¿Cómo funciona Hangfire?

En la página oficial de Hangfire está explicado a detalle cómo funciona, pero en resumen podemos decir que está compuesto por estos 3 componentes:

  • Cliente: Es la aplicación que se encarga de crear las tareas que se van ejecutar, normalmente es nuestra aplicación la cual requiere realizar procesamiento en segundo plano.
  • Almacenamiento: Este es el espacio donde se almacena las tareas; es el lugar donde el cliente lo guarda para que posteriormente sea procesado por el servidor.
  • Servidor: Es la aplicación que obtiene las tareas del almacenamientos y las ejecuta, normalmente en aplicaciones pequeñas, la misma aplicación cliente puede ser servidor (Este es el caso que vamos a ver en el ejemplo más adelante).
Esctructura de HangFire

Arquitectura de Hangfire

Configuración Hangfire para un WebApi

Para empezar con Hangfire en nuestro WebApi, el primer paso es instalar los paquetes NuGet requeridos. Debemos incluir librería de Hangfire como paquete obligatorio y Hangfire.MemoryStorage si deseas almacenar las tareas en memoria como va ser nuestro ejemplo. En caso de preferir Redis o SQL Server, instala el paquete correspondiente.

En el archivo Program.cs, procedemos a la configuración básica de Hangfire. Aunque esta biblioteca ofrece diversas opciones de configuración detalladas en su documentación, nos enfocaremos en las configuraciones mínimas necesarias para mantener el ejemplo simple.

Debemos especificar dónde se almacenarán las tareas, en nuestro caso usaremos el almacenamiento en memoria. Además, necesitamos indicar a la aplicación que inicie un servidor Hangfire. Esto significa que nuestro WebApi de ejemplo sera cliente y servidor Hangfire.

builder.Services.AddHangfire(x => x.UseMemoryStorage());
builder.Services.AddHangfireServer();

Listo! Ahora ya podemos empezar a crear nuestras tareas.

Tipo de tareas

Hangfire permite crear varios tipos de tareas, las que se van a explicar a continuación son las que están disponibles en su versión gratuita.

Disparar y olvidar tarea

Una estrategia común en Hangfire es el enfoque «Disparar y olvidar tarea», que implica crear una tarea sin esperar su resultado inmediato. En este caso, Hangfire se encarga de almacenar la tarea para su ejecución posterior. Este enfoque resulta útil cuando necesitamos realizar una tarea sin detener el flujo principal del proceso, ya que no dependemos del resultado para continuar.

Consideremos un ejemplo práctico de un endpoint que utiliza una tarea en segundo plano para enviar notificaciones.

app.MapPost("/notificacion", () =>
{
var jobId = BackgroundJob.Enqueue(() => SendEmail());
return Results.Accepted(value: new { jobId });
});

En este fragmento de código, el envío de correo se invoca como una tarea en segundo plano. Lo que sucede en realidad es que el cliente agrega una nueva tarea al almacenamiento con el código a ejecutar. Posteriormente, el servidor Hangfire tomará esta tarea y lo ejecutará.

Es fundamental destacar que el método Enqueue, una vez que agrega la tarea al almacenamiento, permite que el proceso principal del cliente continúe sin bloquearse. Por ejemplo, si el método SendEmail demora 10 segundos en ejecutarse, el proceso principal del cliente no se ve afectado. El WebApi proporcionará la respuesta al usuario, mientras que el servidor Hangfire ejecutará la tarea en el momento adecuado.

Si prefieres trabajar con inyección de dependencias puedes usar la interfaz IBackgroundJobClient para invocar el método antes mencionado.

Tareas retrasadas

Las «Tareas retrasadas» en Hangfire operan de manera similar a los tareas de «Disparar y olvidar». La distinción principal radica en la capacidad de establecer un tiempo de espera antes de que la tarea comience.

Consideremos un ejemplo práctico donde se utiliza una tarea retrasada para enviar un correo electrónico después de un período de espera de diez segundos:

app.MapPost("/espera", () =>
{
var jobId = BackgroundJob.Schedule(
() => SendEmail(),
TimeSpan.FromSeconds(10));
return Results.Accepted(value: new { jobId });
});

En este caso, se destaca que el proceso principal no se bloquea durante los diez segundos especificados. Al igual que en el caso anterior, una vez que la tarea se coloca en el almacenamiento, el proceso continúa su ejecución. Después de mínimo 10 segundos, el servidor Hangfire tomará la tarea programada y la ejecutará.

Es importante resaltar el término «mínimo», ya que Hangfire no está revisando constantemente el almacenamiento para buscar tareas pendientes. Por defecto, realiza esta verificación cada 15 segundos (aunque este intervalo puede modificarse). Esta se establece por razones de rendimiento, evitando sobrecargar el servidor. Como resultado, es posible que algunas tareas no se ejecuten exactamente en el momento especificado debido a esto.

Tareas recurrentes

Las «Tareas recurrentes» en Hangfire son similares a lo que comúnmente se conoce como tareas programadas o tareas cron de algunos sistemas operativos. Para implementar este tipo de tareas, utilizamos la función RecurringJob.AddOrUpdate(). Este método nos permite establecer una tarea que se ejecutará periódicamente según la frecuencia y el patrón que definamos.

app.MapPost("/recurrente", () =>
{
RecurringJob.AddOrUpdate(
"send-email",
() => SendEmail(),
Cron.Minutely);
return Results.NoContent();
});

El funcionamiento de los «Tareas recurrentes» en Hangfire es análogo al de un cron. En este contexto, especificamos el trabajo que deseamos ejecutar y, a continuación, definimos con qué frecuencia debe llevarse a cabo. En el ejemplo anterior, estamos indicando que la tarea se ejecute cada minuto. Es crucial tener en consideración que este intervalo de tiempo no es preciso, ya que está sujeto a las mismas consideraciones que mencionamos para el caso de «tareas retrasadas».

Además, es fundamental establecer un identificador único para la tarea recurrente. En nuestro ejemplo, utilizamos el identificador «MyJob». Este identificador no solo sirve para distinguir la tarea en el momento de su creación, sino que también es el identificador para su posterior modificación. La asignación de un identificador único permite la fácil identificación y gestión de tareas recurrentes a medida que evoluciona la aplicación.

Tareas continuas

Si deseamos ejecutar varias tareas y que se ejecuten en secuencia una después de otra, podemos lograrlo con el método ContinueJobWith.

app.MapPost("/continuation", () =>
{
var jobId = BackgroundJob.Enqueue(() => SendEmail());
BackgroundJob.ContinueJobWith(jobId, () => CrearLog());
return Results.Accepted(value: new { jobId });
});

Cuando se crea una tarea, se devuelve un identificador que podemos usar para tener una referencia. Podemos usar ese identificador para por ejemplo se ejecute la tarea para crear un logs después de que se complete la tarea de enviar correo como se muestra en el ejemplo anterior.

Dashboard de Hangfire

Una de las ventajas destacadas de Hangfire es la inclusión de un dashboard que facilita la visualización y gestión del estado de nuestras tareas, permitiéndonos ejecutar y eliminar tareas directamente desde la interfaz, sin necesidad de realizar modificaciones en la aplicación.

En el caso de una aplicación MVC, activar el dashboard es tan simple como incluir la siguiente línea de código en nuestro archivo Program.cs:

app.UseHangfireDashboard();

Una vez habilitado, podemos acceder al dashboard mediante la URL {URL_APP}/hangfire. Al ingresar, nos encontraremos con una interfaz similar a la siguiente, que proporciona una visión detallada del estado de nuestras tareas, incluso podemos hacer operaciones como ejecutar una tarea manualmente o eliminarlo.

Dashboard de hangfire

Dashboard

Podemos cambiar la URL y tener dashboards separados, entre otras configuraciones. No expongas esta interfaz públicamente: permite inspeccionar argumentos y ejecutar operaciones sobre las tareas, por lo que debes protegerla con autorización.

Cuándo usaría Hangfire

Lo usaría cuando una aplicación .NET necesita trabajos persistentes, reintentos y operación visible sin introducir todavía una plataforma de mensajería completa. No lo elegiría por defecto para integrar muchos servicios independientes, mover grandes volúmenes de eventos o resolver flujos que requieren garantías distribuidas más estrictas.

La decisión clave no es solo «ejecutar después», sino definir idempotencia, reintentos, observabilidad y qué ocurre cuando el trabajo falla. Hangfire aporta la infraestructura; esas decisiones siguen siendo responsabilidad del equipo.

Referencias

Jose Antonio Arias
Jose Antonio Arias

Senior .NET Developer e Ingeniero en Informática con amplia experiencia en backend, Azure, DevOps, arquitectura e IA empresarial. Ayudo a convertir problemas y procesos de negocio en soluciones tecnológicas con valor real.