Quartz.NET vs Hangfire: el scheduler adecuado para apps .NET
Quartz.NET y Hangfire resuelven problemas que se solapan pero son distintos en el procesamiento en segundo plano de .NET. Aquí está el verdadero trade-off, y una tercera opción para la parte que es solo "ejecutar esto según un horario".
Todo equipo .NET que ejecuta trabajo programado o en segundo plano termina haciéndose la misma pregunta: ¿Quartz.NET o Hangfire? La respuesta honesta es que se solapan menos de lo que sugieren los artículos comparativos — se construyeron para tareas ligeramente distintas, y elegir mal suele notarse después como fricción, no como una reescritura.
Quartz.NET: un scheduler, ante todo
Quartz.NET es un port de la librería Java Quartz — un motor de
programación. Construido alrededor de ITrigger e IJob, expresiones cron,
calendarios (saltar festivos, ventanas solo en horario laboral) y soporte de
clúster para correr el mismo scheduler en varios nodos sin disparos duplicados.
No te da una cola de jobs, un dashboard, ni un modelo para “encolar esto desde
una petición web y procesarlo después” — no es para eso. Defines jobs y
triggers en código o configuración, y Quartz los dispara.
Si tu problema es realmente “programación precisa, consciente del calendario, en clúster, para trabajo in-process”, Quartz.NET está construido específicamente para eso, de una forma que nada más en el ecosistema .NET iguala del todo.
Hangfire: procesamiento en segundo plano con persistencia
Hangfire parte de un ángulo distinto: procesamiento fiable de jobs en
segundo plano. BackgroundJob.Enqueue(() => SendEmail(id)) desde una acción
de controlador, reintentado automáticamente al fallar, visible en un
dashboard, persistido en SQL Server o Redis para que los jobs sobrevivan a un
reinicio de la app. También soporta jobs recurrentes
(RecurringJob.AddOrUpdate), que es donde empieza a solaparse con Quartz —
pero el diseño basado en almacenamiento y centrado en el dashboard es el
verdadero diferenciador, no la sintaxis de programación.
Si tu problema es “trabajo en segundo plano disparado por eventos de la aplicación, con retries y visibilidad”, Hangfire es la opción más directa — la persistencia y el dashboard ya vienen incluidos.
Dónde se solapan realmente
El solapamiento es estrecho: jobs recurrentes, dirigidos por horario, sin
disparador entrante — un informe nocturno, una tarea de limpieza, un email
de resumen. Quartz.NET puede hacerlo con un CronTrigger. Hangfire puede
hacerlo con RecurringJob.AddOrUpdate. Ambos funcionan. Ninguno es
incorrecto. El factor decisivo suele ser el que tu app ya tenga conectado para
sus otros tipos de jobs — añadir una segunda librería de programación solo
para los jobs recurrentes raramente vale la pena.
La opción que ningún artículo comparativo menciona
Ambas librerías corren dentro de tu proceso de aplicación, lo que significa que la CPU, memoria y conexiones de base de datos de un job recurrente salen del mismo pool que sirve las peticiones. Un job de informes que bloquea filas durante 90 segundos corre en el mismo proceso — y a menudo contra la misma base de datos — que se supone debe responder peticiones HTTP al mismo tiempo.
Para el subconjunto de jobs que son puramente dirigidos por horario y no necesitan los calendarios en clúster de Quartz ni el modelo de cola/ continuation de Hangfire, puedes prescindir de ambos y externalizar el disparador por completo: expón un endpoint HTTP, y haz que algo fuera de tu proceso lo llame según el horario, con su propio retry/backoff y registro de ejecuciones. Esa es toda la idea detrás de SteadyCron — consulta el desglose completo en SteadyCron vs Hangfire para ver dónde cae realmente ese trade-off, incluso para jobs que quieras seguir dejando a Quartz.NET o Hangfire.
Elegir uno
- Quartz.NET si necesitas programación consciente del calendario o en clúster para jobs in-process, y no necesitas una cola de jobs.
- Hangfire si necesitas una cola de jobs con persistencia y dashboard, y los jobs recurrentes son una función secundaria que usarás encima de eso.
- Ninguno, para el subconjunto puramente programado, si el job no necesita correr dentro de tu app en absoluto — sacar el disparador elimina por completo la librería de programación y su almacenamiento de la ecuación.