Skip to main content

Programación de tareas

Visión general

Crono cuenta con un sistema de tareas programadas que ejecuta procesos automáticos —como generar moras, enviar correos, calcular históricos de cartera o cerrar cajas— en momentos determinados. Es especialmente útil para procesos largos o intensivos.

El programador usa un timer que cada minuto comprueba si hay tareas vencidas y las ejecuta como parte de una solicitud HTTP, de modo que el ámbito de dependencias esté siempre disponible. Durante la ejecución, el TareaContextoVirtualizador virtualiza algunos parámetros del entorno (por ejemplo, el usuario de sistema y la organización principal).

Descriptor de tarea

La entidad TareaDescriptor define los metadatos de cada tarea. Algunos valores (como la expresión cron y el estado habilitado) los puede editar el usuario en el backend:

PropiedadDescripción
NombreNombre visible de la tarea.
AliasAlias opcional.
CronExpresionExpresión cron que determina la próxima ejecución.
TipoTipo de la clase de tarea a ejecutar.
HabilitadoSi la tarea está activa.
PrioridadTareaPrioridad (Normal por defecto).
DetenerCasoErrorSi es true, la tarea se deshabilita al fallar hasta que el usuario la reactive.
SiguienteEjecucionUtcPróxima ejecución calculada.
OcultoOculta la tarea de la interfaz (solo uso interno).
EjecutarPorMaquinaVer arrendamiento más abajo.
UltimaEjecucionInformación de la última ejecución (TareaEjecucionInformacion).

Tras cada ejecución se crea una entrada de historial con la hora, duración, nombre de máquina y errores. El usuario también puede disparar o cancelar una tarea manualmente desde el backend.

Implementar una tarea

Crea una clase concreta que implemente ITarea. No hace falta registrarla en la inyección de dependencias: se detecta y registra automáticamente como servicio con ámbito al iniciar la aplicación, por lo que puede tomar dependencias.

El método Ejecutar es el manejador (no hay versión síncrona). Recibe el TareaEjecucionContexto y el CancellationToken:

public class MiTarea : ITarea
{
private readonly CronoDbContexto _db;

public MiTarea(CronoDbContexto db)
{
_db = db;
}

public async Task Ejecutar(TareaEjecucionContexto ctx, CancellationToken cancelToken = default)
{
// Proceso de la tarea...
}
}

Por convención, el nombre del tipo (sin espacio de nombres) es TareaDescriptor.Tipo. Puedes decorar la clase con TareaNombreAttribute para asignarle otro nombre y evitar conflictos.

info

Si en un mismo sondeo hay más de una tarea vencida, se ejecutan una tras otra, no en paralelo. Si una tarea no ha terminado para el siguiente sondeo (un minuto después), esa iteración se omite. Por eso no tiene sentido definir expresiones cron con frecuencia menor a un minuto.

Ejemplos reales en Crono: VerificarEjecucionTareasTarea (supervisa que las tareas críticas se ejecuten), GuardarBalanceGeneralHistoricoTarea, la generación diaria de moras y el envío de resúmenes.

Cancelación

El parámetro CancellationToken combina el token de apagado de la aplicación con el de cancelación del usuario. No es necesario comprobarlo en cada iteración; basta con revisarlo tras completar un lote de trabajo, o lanzar la excepción correspondiente con cancelToken.ThrowIfCancellationRequested().

Progreso

Para mostrar el progreso en la interfaz, usa el TareaEjecucionContexto, que expone EstablecerProgreso / EstablecerProgresoAsync (con varias sobrecargas: mensaje, porcentaje, o valor/máximo). El progreso se guarda de inmediato y la interfaz lo consulta cada segundo:

await ctx.EstablecerProgresoAsync(procesados, total, "Procesando...");

El contexto también ofrece Parametros (diccionario de parámetros de la ejecución), TareaAlmacen y los ayudantes Resolver<T>() / ResolverPorNombre<T>().

Agregar o quitar tareas por código

Si tu módulo aporta tareas, debes agregarlas al almacén durante la instalación y quitarlas en la desinstalación, usando ITareaAlmacen:

internal class Modulo : ModuloBase
{
private readonly ITareaAlmacen _tareaAlmacen;

public Modulo(ITareaAlmacen tareaAlmacen)
{
_tareaAlmacen = tareaAlmacen;
}

public override async Task InstalarAsync(/* contexto */)
{
// Agregar la tarea si aún no existe
await _tareaAlmacen.ObtenerOAgregarTareaAsync<MiTarea>(x =>
{
x.Nombre = "Nombre visible de mi tarea";
x.CronExpresion = "0 */1 * * *"; // cada hora
x.Habilitado = true;
});
}
}

Ejecutar una tarea por código

Para ejecutar una sola tarea de forma programática, usa ITareaProgramador.EjecutarUnaTareaAsync() pasando el TareaDescriptor.Id. Puedes pasar parámetros opcionales (diccionario) que se convierten en query string y quedan disponibles en TareaEjecucionContexto.Parametros:

var tarea = await _tareaAlmacen.ObtenerTareaPorTipoAsync(nameof(MiTarea));
if (tarea != null)
{
// Disparar la ejecución (no se espera: el programador delega vía HttpClient)
_ = _tareaProgramador.EjecutarUnaTareaAsync(tarea.Id, new Dictionary<string, string>
{
{ "AlgunParametro", valor.ToString() }
});
}

Arrendamiento de tareas

Por defecto, las tareas se ejecutan en exclusiva: quedan bloqueadas durante su ejecución, de modo que en una granja de servidores solo un servidor ejecuta cada tarea (el primero gana; los demás la omiten). Para cambiarlo y permitir que cada servidor la ejecute en paralelo, establece TareaDescriptor.EjecutarPorMaquina = true. Tiene sentido cuando la tarea hace algo que debe ocurrir en cada servidor (limpiar archivos locales, reconstruir índices replicados, etc.).

info

El usuario no puede editar EjecutarPorMaquina desde el backend.