¿Cómo puedo programar un servicio de Windows C # para realizar una tarea a diario?

84

Tengo un servicio escrito en C # (.NET 1.1) y quiero que realice algunas acciones de limpieza a medianoche todas las noches. Tengo que mantener todo el código contenido dentro del servicio, entonces, ¿cuál es la forma más fácil de lograr esto? ¿Uso Thread.Sleep()y comprobación del tiempo de renovación?

ctrlalt3nd
fuente
10
¿Realmente desea crear un proceso .NET que permanezca en la memoria todo el día y haga algo a la medianoche? ¿Por qué exactamente no puedes, al instalar tu herramienta, configurar un evento del sistema que se activa a la medianoche (o en algún otro momento configurable) que inicia tu aplicación, hace su trabajo y luego desaparece?
Robert P
6
Sé que me enojaría un poco si descubrí que tengo un servicio administrado que consume entre 20 y 40 megas de memoria, funcionando todo el tiempo, que no hace nada excepto una vez al día. :)
Robert P
su enlace
csa

Respuestas:

86

No usaría Thread.Sleep (). Utilice una tarea programada (como han mencionado otros) o configure un temporizador dentro de su servicio, que se activa periódicamente (cada 10 minutos, por ejemplo) y verifique si la fecha cambió desde la última ejecución:

private Timer _timer;
private DateTime _lastRun = DateTime.Now.AddDays(-1);

protected override void OnStart(string[] args)
{
    _timer = new Timer(10 * 60 * 1000); // every 10 minutes
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    _timer.Start();
    //...
}


private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // ignore the time, just compare the date
    if (_lastRun.Date < DateTime.Now.Date)
    {
        // stop the timer while we are running the cleanup task
        _timer.Stop();
        //
        // do cleanup stuff
        //
        _lastRun = DateTime.Now;
        _timer.Start();
    }
}
M4N
fuente
28
¿No tendría más sentido configurar el temporizador para que expire a la medianoche, en lugar de despertarse cada minuto para comprobar la hora?
Kibbee
11
@Kibbee: tal vez si el servicio no se está ejecutando a la medianoche (por cualquier motivo), es posible que desee ejecutar la tarea tan pronto como el servicio se esté ejecutando nuevamente.
M4N
4
Luego, debe agregar algún código en el inicio del servicio para averiguar si debe ejecutarlo de inmediato, porque el servicio no se estaba ejecutando, y debería estarlo. No debería revisar cada 10 minutos continuamente. Compruebe si debe ejecutarse en el inicio y, si no, averigüe la próxima vez que se ejecute. después de eso, y configure un temporizador durante el tiempo que necesite.
Kibbee
3
@Kibbee: sí, estoy de acuerdo (es un detalle menor que estamos discutiendo). Pero incluso si el temporizador se dispara cada 10 segundos, no haría ningún daño (es decir, no consumiría mucho tiempo).
M4N
1
buena solución, pero falta el código anterior _timer.start () y _lastRun debe establecerse en ayer de esta manera: private DateTime _lastRun = DateTime.Now.AddDays (-1);
Zulu Z
72

Consulte Quartz.NET . Puede usarlo dentro de un servicio de Windows. Le permite ejecutar un trabajo según un programa configurado, e incluso admite una sintaxis simple de "trabajo cron". He tenido mucho éxito con eso.

Aquí hay un ejemplo rápido de su uso:

// Instantiate the Quartz.NET scheduler
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();

// Instantiate the JobDetail object passing in the type of your
// custom job class. Your class merely needs to implement a simple
// interface with a single method called "Execute".
var job = new JobDetail("job1", "group1", typeof(MyJobClass));

// Instantiate a trigger using the basic cron syntax.
// This tells it to run at 1AM every Monday - Friday.
var trigger = new CronTrigger(
    "trigger1", "group1", "job1", "group1", "0 0 1 ? * MON-FRI");

// Add the job to the scheduler
scheduler.AddJob(job, true);
scheduler.ScheduleJob(trigger);
jeremcc
fuente
2
Una buena sugerencia: tan pronto como hagas algo incluso un poco más complejo que las cosas básicas del temporizador, deberías mirar Quartz.
serg10
2
Quartz es tan fácil de usar que diría que incluso para una tarea súper simple, simplemente adelante y úselo. Le dará la posibilidad de ajustar fácilmente la programación.
Andy White
Super simple no es realmente? La mayoría de los novatos detectarán este problema aquí stackoverflow.com/questions/24628372/…
Emil
21

¿Una tarea diaria? Parece que debería ser una tarea programada (panel de control), no es necesario un servicio aquí.

Marc Gravell
fuente
15

¿Tiene que ser un servicio real? ¿Puedes usar las tareas programadas integradas en el panel de control de Windows?

Ian Jacobs
fuente
Dado que el servicio ya existe, podría ser más fácil agregar la funcionalidad allí.
M4N
1
Convertir un servicio de propósito limitado como este en una aplicación de consola debería ser trivial.
Stephen Martin
7
Ya dijo: "Tengo que mantener todo el código contenido en el servicio". No creo que sea necesario cuestionar los requisitos originales. Depende, pero a veces hay muy buenas razones para utilizar un servicio en lugar de una tarea programada.
jeremcc
3
+1: Porque siempre necesitas mirar tus problemas desde todos los lados.
GvS
6
Si realmente solo tuviera que ejecutar una tarea por día, supongo que una aplicación de consola simple que se ejecute con una tarea programada estaría bien. Mi punto es que todo depende de la situación, y decir "No use un servicio de Windows" no es una respuesta útil en mi opinión.
jeremcc
3

La forma en que logro esto es con un temporizador.

Ejecute un temporizador de servidor, haga que compruebe la hora / minuto cada 60 segundos.

Si es la hora / minuto correcta, ejecute su proceso.

De hecho, tengo esto resumido en una clase base que llamo OnceADayRunner.

Déjame limpiar un poco el código y lo publicaré aquí.

    private void OnceADayRunnerTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        using (NDC.Push(GetType().Name))
        {
            try
            {
                log.DebugFormat("Checking if it's time to process at: {0}", e.SignalTime);
                log.DebugFormat("IsTestMode: {0}", IsTestMode);

                if ((e.SignalTime.Minute == MinuteToCheck && e.SignalTime.Hour == HourToCheck) || IsTestMode)
                {
                    log.InfoFormat("Processing at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                    OnceADayTimer.Enabled = false;
                    OnceADayMethod();
                    OnceADayTimer.Enabled = true;

                    IsTestMode = false;
                }
                else
                {
                    log.DebugFormat("Not correct time at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                }
            }
            catch (Exception ex)
            {
                OnceADayTimer.Enabled = true;
                log.Error(ex.ToString());
            }

            OnceADayTimer.Start();
        }
    }

La carne de res del método está en la verificación e.SignalTime.Minute / Hour.

Hay ganchos allí para probar, etc. pero así es como podría verse su temporizador transcurrido para que todo funcione.

CubanX
fuente
Un ligero retraso debido a un servidor ocupado podría hacer que se pierda la hora + minuto.
Jon B
1
Cierto. Cuando hice esto, verifiqué: ¿Es la hora ahora mayor que 00:00? Si es así, ¿han pasado más de 24 horas desde la última vez que ejecuté el trabajo? Si es así, ejecute el trabajo; de lo contrario, espere nuevamente.
ZombieSheep
2

Como ya escribieron otros, un temporizador es la mejor opción en el escenario que describió.

Dependiendo de sus requisitos exactos, es posible que no sea necesario verificar la hora actual cada minuto. Si no necesita realizar la acción exactamente a la medianoche, sino solo una hora después de la medianoche, puede optar por el enfoque de Martin de solo verificar si la fecha ha cambiado.

Si la razón por la que desea realizar su acción a la medianoche es que espera una carga de trabajo baja en su computadora, es mejor que tenga cuidado: los demás suelen hacer la misma suposición y, de repente, tiene 100 acciones de limpieza que comienzan entre las 0:00 y las 0:00. : 01 am

En ese caso, debería considerar comenzar su limpieza en un momento diferente. Normalmente hago esas cosas no a la hora del reloj, sino a las medias horas (la 1.30 es mi preferencia personal)

Treb
fuente
1

Le sugiero que use un temporizador, pero configúrelo para que verifique cada 45 segundos, no cada minuto. De lo contrario, puede encontrarse con situaciones en las que, con una carga pesada, se pierda la verificación de un minuto en particular, porque entre el momento en que se activa el temporizador y el momento en que se ejecuta su código y verifica la hora actual, es posible que haya perdido el minuto objetivo.

GWLlosa
fuente
Si no está comprobando para asegurarse de que no se haya ejecutado ya una vez, existe la posibilidad de que pueda marcar 00:00 dos veces si está comprobando cada 45 segundos.
Michael Meadows
Buen punto. Este problema podría evitarse llamando a Sleep (15000) al final del procedimiento de verificación. (o quizás 16000 ms, solo para estar seguro ;-)
Treb
Estoy de acuerdo, debe verificar si ya está en ejecución, independientemente de la duración del temporizador que elija.
GWLlosa
0

Para aquellos que encontraron que las soluciones anteriores no funcionan, es porque puede tener un thisdentro de su clase, lo que implica un método de extensión que, como dice el mensaje de error, solo tiene sentido en una clase estática no genérica. Tu clase no es estática. Esto no parece ser algo que tenga sentido como método de extensión, ya que actúa en la instancia en cuestión, así que elimine elthis .

Fandango68
fuente
-2

Prueba esto:

public partial class Service : ServiceBase
{
    private Timer timer;
    public Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        SetTimer();
    }

    private void SetTimer()
    {
        if (timer == null)
        {
            timer = new Timer();
            timer.AutoReset = true;
            timer.Interval = 60000 * Convert.ToDouble(ConfigurationManager.AppSettings["IntervalMinutes"]);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }
    }

    private void timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        //Do some thing logic here
    }

    protected override void OnStop()
    {
        // disposed all service objects
    }
}
Shailendra Tiwari
fuente