2010-01-27 9 views
8

Estoy buscando implementar un proceso "Heartbeat" para realizar muchas tareas de limpieza repetidas a lo largo del día.¿Cómo cambiarías mi proceso Heartbeat escrito en C#?

Esto parecía una buena oportunidad de utilizar el patrón de comandos, así que tengo una interfaz que se parece a:

public interface ICommand 
    { 
     void Execute(); 
     bool IsReady(); 
    } 

continuación, he creado varias tareas que quiero ser ejecutado. Aquí está un ejemplo básico:

public class ProcessFilesCommand : ICommand 
{ 
    private int secondsDelay; 
    private DateTime? lastRunTime; 

    public ProcessFilesCommand(int secondsDelay) 
    { 
     this.secondsDelay = secondsDelay; 
    } 

    public void Execute() 
    { 
     Console.WriteLine("Processing Pending Files..."); 
     Thread.Sleep(5000); // Simulate long running task 
     lastRunTime = DateTime.Now; 
    } 

    public bool IsReady() 
    { 
     if (lastRunTime == null) return true; 

     TimeSpan timeSinceLastRun = DateTime.Now.Subtract(lastRunTime.Value); 
     return (timeSinceLastRun.TotalSeconds > secondsDelay); 
    } 

} 

Por último, mi aplicación de consola se ejecuta en este bucle de espera en busca de tareas para añadir a la ThreadPool:

class Program 
{ 
    static void Main(string[] args) 
    { 

     bool running = true; 

     Queue<ICommand> taskList = new Queue<ICommand>(); 
     taskList.Enqueue(new ProcessFilesCommand(60)); // 1 minute interval 
     taskList.Enqueue(new DeleteOrphanedFilesCommand(300)); // 5 minute interval 

     while (running) 
     { 
      ICommand currentTask = taskList.Dequeue(); 
      if (currentTask.IsReady()) 
      { 
       ThreadPool.QueueUserWorkItem(t => currentTask.Execute()); 
      } 
      taskList.Enqueue(currentTask); 
      Thread.Sleep(100); 
     } 

    } 
} 

no tengo mucha experiencia con multi-threading más allá de algún trabajo que hice en la clase de Sistemas Operativos. Sin embargo, hasta donde puedo decir, ninguno de mis hilos está accediendo a ningún estado compartido, por lo que deberían estar bien.

¿Esto parece un diseño "OK" para lo que quiero hacer? ¿Hay algo que cambiarías?

Respuesta

10

Este es un gran comienzo. Hemos hecho muchas cosas como esta recientemente, por lo que puedo ofrecer algunas sugerencias.

  1. No utilice el grupo de subprocesos para tareas de larga ejecución. El grupo de subprocesos está diseñado para ejecutar muchas pequeñas tareas pequeñas. Si está realizando tareas de larga ejecución, use un hilo por separado. Si pasas hambre en el grupo de subprocesos (agota todas las tareas), todo lo que se pone en cola solo espera a que esté disponible un subproceso de subprocesos, lo que afecta significativamente el rendimiento efectivo del subproceso de subprocesos.

  2. Haga que la rutina Main() lleve un registro de cuándo se ejecutaron las cosas y cuánto tiempo tardará en ejecutarse cada una. En lugar de que cada comando diga "sí, estoy listo" o "no, no soy", que será el mismo para cada comando, solo tiene los campos LastRun e Interval que Main() puede usar para determinar cuándo se debe ejecutar cada comando .

  3. No utilice una cola. Si bien puede parecer una operación de tipo Queue, dado que cada comando tiene su propio intervalo, realmente no es una cola normal. En su lugar, coloque todos los comandos en una lista y luego ordene la lista por el tiempo más corto hasta la próxima ejecución. Duerma el hilo hasta que se necesite el primer comando para ejecutarlo. Ejecuta ese comando. Recurra a la lista por el siguiente comando para ejecutar. Dormir. Repetir.

  4. No utilice múltiples hilos. Si el intervalo de cada comando es de un minuto o de unos pocos minutos, probablemente no necesite utilizar ningún hilo. Puede simplificar haciendo todo en el mismo hilo.

  5. Error al manejar. Este tipo de cosas necesita un manejo de errores extenso para asegurarse de que un problema en un comando no haga que todo el ciclo falle y, por lo tanto, puede solucionar un problema cuando ocurra. También es posible que desee decidir si un comando debe volverse a intentar inmediatamente en caso de error o esperar hasta la próxima ejecución programada, o incluso retrasarlo más de lo normal. También es posible que no desee registrar un error en un comando si el error ocurre cada vez (un error en un comando que se ejecuta a menudo puede crear fácilmente enormes archivos de registro).

+3

# 1: me gustaría agregar que literalmente puede quedarse sin hilos de grupo de subprocesos, lo que hace que su aplicación se bloquee por completo. Esta es la razón por la cual nunca permites que los hilos entren en tu ThreadPool. –

+0

+1 para el punto sobre tareas de ejecución corta para el grupo de subprocesos. –

+0

@Jonathan Allen, buen punto, agregué una nota al respecto en mi respuesta. –

0

running variable tendrá que marcarse como volatile si su estado va a ser cambiado por otro hilo.

En cuanto a la idoneidad, ¿por qué no utilizar un temporizador?

+0

Estaría más tentado de usar un temporizador también, y configure el temporizador para cuando venza la próxima tarea (que usted debería poder descifrar). No tiene sentido controlar tu tarea cada 1/10 de segundo. – kyoryu

+0

No, no es así. El peor escenario posible es que pases por un ciclo extra del ciclo. Esto no es un gran problema teniendo en cuenta que podría suceder de todos modos debido a la carrera entre dar vuelta la bandera y comprobarlo. –

+0

@Jonathan Allen: Sin la palabra clave volátil, la ejecución podría almacenarse en caché en un registro. –

2

me gustaría hacer todas las clases de comando immutable para asegurar que usted no tiene que preocuparse por los cambios en el estado.

+0

Las clases de comando publicadas ya tienen campos que cambian, por lo que no pueden ser inmutables como están. –

+0

@Sam Sí, estoy de acuerdo. Pero esto se puede rediseñar para hacer que la clase sea inmutable. – LWoodyiii

6

En lugar de escribir todo desde cero, puede optar por crear su aplicación utilizando un marco que maneje toda la programación y el subproceso por usted. La biblioteca de código abierto NCron está diseñada para este propósito y es muy fácil de usar.

definir su trabajo como este:

class MyFirstJob : CronJob 
{ 
    public override void Execute() 
    { 
     // Put your logic here. 
    } 
} 

y crear un punto de entrada principal para su aplicación incluyendo la configuración de la programación de la siguiente manera:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Bootstrap.Init(args, ServiceSetup); 
    } 

    static void ServiceSetup(SchedulingService service) 
    { 
     service.Hourly().Run<MyFirstJob>(); 
     service.Daily().Run<MySecondJob>(); 
    } 
} 

Ésta es todo el código que necesitará escribe si eliges seguir por este camino. También tiene la opción de hacer more complex schedules o dependency injection si es necesario, y logging se incluye de fábrica.

Descargo de responsabilidad: ¡Soy el programador principal de NCron, así que podría ser un poco parcial! ;-)

+1

¡Esta es una muy buena sugerencia y por eso NCron se ve muy bien! Creo que al final terminaré yendo por esta ruta. ¿Crees que puedes tocar la base en el código provisto y otras sugerencias hechas acerca de que las personas también hagan lo propio? Estás en una posición única de experiencia. – mmcdole

+1

+1 por cierto. Gracias por presentarme a NCron. – mmcdole

2

Ahora, las "Extensiones paralelas" de un día de Microsoft deben ser la opción viable para escribir código simultáneo o realizar tareas relacionadas con subprocesos. Proporciona una buena abstracción sobre el grupo de subprocesos y los subprocesos del sistema de manera que no necesita pensar de manera imperativa para realizar la tarea.

En mi opinión, considere usarlo. Por cierto, tu código está limpio.

Gracias.

Cuestiones relacionadas