Come posso pianificare un servizio Windows C # per eseguire un'attività ogni giorno?


84

Ho un servizio scritto in C # (.NET 1.1) e desidero che esegua alcune azioni di pulizia a mezzanotte ogni notte. Devo mantenere tutto il codice contenuto nel servizio, quindi qual è il modo più semplice per farlo? Uso Thread.Sleep()e controllo del tempo che scorre?


10
Vuoi davvero creare un processo .NET che rimanga nella memoria, tutto il giorno, e faccia qualcosa a mezzanotte? Perché esattamente non puoi, dopo aver installato il tuo strumento, impostare un evento di sistema che si attiva a mezzanotte (o un altro orario configurabile) che avvia la tua app, fa il suo dovere e poi scompare?
Robert P

6
So che sarei un po 'incazzato se scoprissi di avere un servizio gestito che consuma 20-40 mega di memoria, sempre in esecuzione, che non fa nulla tranne una volta al giorno. :)
Robert P

il suo link
csa

Risposte:


86

Non userei Thread.Sleep (). Utilizza un'attività pianificata (come altri hanno menzionato) o imposta un timer all'interno del tuo servizio, che si attiva periodicamente (ad esempio ogni 10 minuti) e controlla se la data è cambiata dall'ultima esecuzione:

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();
    }
}

28
Non avrebbe più senso impostare il timer in modo che scada a mezzanotte, invece di svegliarsi ogni minuto per controllare l'ora?
Kibbee

11
@ Kibbee: forse se il servizio non è in esecuzione a mezzanotte (per qualsiasi motivo), potresti voler eseguire l'attività non appena il servizio è di nuovo in esecuzione.
M4N

4
Quindi dovresti aggiungere del codice all'avvio del servizio per capire se dovresti eseguire subito, perché il servizio non era in esecuzione, e dovrebbe esserlo. Non dovresti controllare continuamente ogni 10 minuti. Controlla se dovresti eseguire all'avvio, quindi in caso contrario calcola la prossima volta per eseguire. dopodiché, e imposta un timer per tutto il tempo necessario.
Kibbee

3
@Kibbee: sì, sono d'accordo (è un piccolo dettaglio di cui stiamo discutendo). Ma anche se il timer si attiva ogni 10 secondi, non farebbe alcun danno (cioè non richiede tempo).
M4N

1
buona soluzione ma manca il codice sopra _timer.start () e _lastRun dovrebbe essere impostato su ieri in questo modo: private DateTime _lastRun = DateTime.Now.AddDays (-1);
Zulu Z

72

Dai un'occhiata a Quartz.NET . Puoi usarlo all'interno di un servizio Windows. Consente di eseguire un lavoro in base a una pianificazione configurata e supporta anche una semplice sintassi "cron job". Ho avuto molto successo con esso.

Ecco un rapido esempio del suo utilizzo:

// 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);

2
Un buon suggerimento: non appena fai qualcosa di anche leggermente più complesso delle cose di base del timer, dovresti guardare Quartz.
serg10

2
Il quarzo è così facile da usare, direi che anche per un compito semplicissimo, vai avanti e usalo. Darà la possibilità di regolare facilmente la programmazione.
Andy White,

Super semplice non è davvero? La maggior parte dei neofiti coglierà questo problema qui stackoverflow.com/questions/24628372/…
Emil

21

Un compito quotidiano? Sembra che dovrebbe essere solo un'attività pianificata (pannello di controllo), non è necessario un servizio qui.


15

Deve essere un servizio effettivo? Puoi semplicemente usare le attività pianificate integrate nel pannello di controllo di Windows.


Poiché il servizio esiste già, potrebbe essere più semplice aggiungere la funzionalità lì.
M4N

1
La conversione di un servizio per scopi ristretti come questo in un'app console dovrebbe essere banale.
Stephen Martin,

7
Ha già detto: "Devo mantenere tutto il codice contenuto nel servizio". Non credo sia necessario mettere in discussione i requisiti originali. Dipende, ma a volte ci sono ottimi motivi per utilizzare un servizio su un'attività pianificata.
jeremcc

3
+1: Perché devi sempre considerare i tuoi problemi da tutte le parti.
GvS

6
Se avessi davvero una sola attività da eseguire al giorno, immagino che una semplice app per console eseguita con un'attività pianificata andrebbe bene. Il punto è che tutto dipende dalla situazione e dire "Non utilizzare un servizio Windows" non è una risposta utile IMO.
jeremcc

3

Il modo in cui riesco a farlo è con un timer.

Esegui un timer del server, fallo controllare l'ora / i minuti ogni 60 secondi.

Se è l'ora / minuto giusto, esegui il processo.

In realtà ho questo estratto in una classe base che chiamo OnceADayRunner.

Fammi pulire un po 'il codice e lo posterò qui.

    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 del metodo è nel controllo e.SignalTime.Minute / Hour.

Ci sono degli hook per i test, ecc. Ma questo è l'aspetto che potrebbe avere il timer trascorso per far funzionare tutto.


Un leggero ritardo dovuto a un server occupato potrebbe far perdere l'ora + il minuto.
Jon B

1
Vero. Quando l'ho fatto, ho controllato: l'ora ora è maggiore di 00:00? In tal caso, sono trascorse più di 24 ore dall'ultima volta che ho eseguito il lavoro? In tal caso, eseguire il lavoro, altrimenti attendere di nuovo.
ZombieSheep

2

Come altri hanno già scritto, un timer è l'opzione migliore nello scenario che hai descritto.

A seconda delle tue esatte esigenze, potrebbe non essere necessario controllare l'ora corrente ogni minuto. Se non è necessario eseguire l'azione esattamente a mezzanotte, ma solo entro un'ora dopo la mezzanotte, è possibile utilizzare l'approccio di Martin di controllare solo se la data è cambiata.

Se il motivo per cui vuoi eseguire la tua azione a mezzanotte è che ti aspetti un carico di lavoro basso sul tuo computer, è meglio che tu stia attento: lo stesso presupposto è spesso fatto da altri e improvvisamente hai 100 azioni di pulizia che iniziano tra 0:00 e 0 : 01 am

In tal caso, dovresti considerare di iniziare la pulizia in un momento diverso. Di solito faccio queste cose non all'ora dell'orologio, ma a mezz'ora (1.30 è la mia preferenza personale)


1

Suggerirei di utilizzare un timer, ma impostarlo per controllare ogni 45 secondi, non ogni minuto. Altrimenti puoi imbatterti in situazioni in cui con un carico pesante, il controllo di un particolare minuto viene perso, perché tra il momento in cui il timer si attiva e il tempo in cui il tuo codice viene eseguito e controlla l'ora corrente, potresti aver perso il minuto target.


Se non stai controllando per assicurarti che non sia già stato eseguito una volta, c'è la possibilità che potresti premere 00:00 due volte se controlli ogni 45 secondi.
Michael Meadows

Buon punto. Questo problema potrebbe essere evitato chiamando Sleep (15000) al termine della procedura di controllo. (o forse 16000 ms, giusto per andare sul sicuro ;-)
Treb

Sono d'accordo, dovresti controllare se è già in esecuzione, indipendentemente dalla durata del timer che scegli.
GWLlosa


0

Per coloro che hanno trovato le soluzioni di cui sopra non funzionanti, è perché potresti avere un thisinterno alla tua classe, il che implica un metodo di estensione che, come dice il messaggio di errore, ha senso solo su una classe statica non generica. La tua classe non è statica. Questo non sembra essere qualcosa che abbia senso come metodo di estensione, poiché agisce sull'istanza in questione, quindi rimuovi il file this.


-2

Prova questo:

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
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.