Miglior timer per l'utilizzo in un servizio Windows


108

Ho bisogno di creare un servizio Windows che verrà eseguito ogni N periodo di tempo.
La domanda è:
quale controllo del timer dovrei usare: System.Timers.Timero System.Threading.Timeruno? Influenza su qualcosa?

Lo chiedo perché ho sentito molte prove di un funzionamento non corretto dei System.Timers.Timerservizi Windows.
Grazie.

Risposte:


118

Entrambi System.Timers.Timere System.Threading.Timerfunzioneranno per i servizi.

I timer che si desidera evitare sono System.Web.UI.Timere System.Windows.Forms.Timer, rispettivamente per applicazioni ASP e WinForms. L'utilizzo di questi farà sì che il servizio carichi un assembly aggiuntivo che non è realmente necessario per il tipo di applicazione che si sta creando.

Usa System.Timers.Timercome il seguente esempio (inoltre, assicurati di utilizzare una variabile a livello di classe per prevenire la garbage collection, come indicato nella risposta di Tim Robinson):

using System;
using System.Timers;

public class Timer1
{
    private static System.Timers.Timer aTimer;

    public static void Main()
    {
        // Normally, the timer is declared at the class level,
        // so that it stays in scope as long as it is needed.
        // If the timer is declared in a long-running method,  
        // KeepAlive must be used to prevent the JIT compiler 
        // from allowing aggressive garbage collection to occur 
        // before the method ends. (See end of method.)
        //System.Timers.Timer aTimer;

        // Create a timer with a ten second interval.
        aTimer = new System.Timers.Timer(10000);

        // Hook up the Elapsed event for the timer.
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

        // Set the Interval to 2 seconds (2000 milliseconds).
        aTimer.Interval = 2000;
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program.");
        Console.ReadLine();

        // If the timer is declared in a long-running method, use
        // KeepAlive to prevent garbage collection from occurring
        // before the method ends.
        //GC.KeepAlive(aTimer);
    }

    // Specify what you want to happen when the Elapsed event is 
    // raised.
    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

/* This code example produces output similar to the following:

Press the Enter key to exit the program.
The Elapsed event was raised at 5/20/2007 8:42:27 PM
The Elapsed event was raised at 5/20/2007 8:42:29 PM
The Elapsed event was raised at 5/20/2007 8:42:31 PM
...
 */

Se scegli System.Threading.Timer, puoi utilizzare come segue:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        AutoResetEvent autoEvent     = new AutoResetEvent(false);
        StatusChecker  statusChecker = new StatusChecker(10);

        // Create the delegate that invokes methods for the timer.
        TimerCallback timerDelegate = 
            new TimerCallback(statusChecker.CheckStatus);

        // Create a timer that signals the delegate to invoke 
        // CheckStatus after one second, and every 1/4 second 
        // thereafter.
        Console.WriteLine("{0} Creating timer.\n", 
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = 
                new Timer(timerDelegate, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every 
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period.\n");

        // When autoEvent signals the second time, dispose of 
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    int invokeCount, maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}

Entrambi gli esempi provengono dalle pagine MSDN.


1
Perché suggerisci: GC.KeepAlive (aTimer) ;, aTimer è la variabile di istanza giusta, quindi per esempio se è una variabile di istanza di forma, ci sarà sempre riferimento ad essa finché c'è forma, no?
giorgim

37

Non utilizzare un servizio per questo. Creare una normale applicazione e creare un'attività pianificata per eseguirla.

Questa è la migliore pratica comunemente considerata. Jon Galloway è d'accordo con me. O forse è il contrario. In ogni caso, il fatto è che non è consigliabile creare un servizio Windows per eseguire un'attività intermittente eseguita da un timer.

"Se stai scrivendo un servizio Windows che esegue un timer, dovresti rivalutare la tua soluzione."

–Jon Galloway, Community Program Manager ASP.NET MVC, autore, supereroe part-time


26
Se il servizio deve essere eseguito tutto il giorno, forse ha senso un servizio piuttosto che un'attività pianificata. Oppure, disporre di un servizio potrebbe semplificare l'amministrazione e la registrazione per un gruppo di infrastrutture che non è così esperto come il team dell'applicazione. Tuttavia, mettere in discussione il presupposto che un servizio sia richiesto è del tutto valido e queste valutazioni negative sono immeritate. +1 a entrambi.
sfuqua

2
@mr Nonsense. Le attività pianificate sono una parte fondamentale del sistema operativo. Per favore tieni il tuo FUD per te.

4
@ MR: non sono un evangelista, sono un realista. E la realtà mi ha insegnato che le attività programmate non sono "estremamente buggate". In effetti, spetta a te come persona che ha affermato di sostenerlo. Altrimenti, tutto ciò che stai facendo è diffondere paura, incertezza e dubbio.

13
Odio entrare nel fango qui, ma devo difendere un po 'MR. Nella mia azienda sono in esecuzione diverse applicazioni critiche che sono app per console Windows. Ho usato l'Utilità di pianificazione di Windows per eseguirli tutti. In almeno 5 occasioni ora, abbiamo avuto un problema in cui il servizio Scheduler "si è confuso" in qualche modo. Le attività non sono state eseguite e alcune erano in uno stato strano. L'unica soluzione era il riavvio del server o l'arresto e l'avvio del servizio Scheduler. Non qualcosa che posso fare senza i diritti di amministratore e non è accettabile in un ambiente di produzione. Solo i miei $ 0,02.
SpaceCowboy74

6
In realtà mi è successo su alcuni server (3 finora). Non dirò però che è la norma. Dico solo che a volte non è male implementare il proprio metodo di fare qualcosa.
SpaceCowboy74

7

Uno dei due dovrebbe funzionare bene. Infatti, System.Threading.Timer utilizza System.Timers.Timer internamente.

Detto questo, è facile abusare di System.Timers.Timer. Se non si memorizza l'oggetto Timer in una variabile da qualche parte, è possibile che venga raccolto dalla spazzatura. In tal caso, il timer non si attiverà più. Chiama il metodo Dispose per arrestare il timer oppure usa la classe System.Threading.Timer, che è un wrapper leggermente più gradevole.

Quali problemi hai riscontrato finora?


Mi chiedo perché un'app per Windows Phone possa accedere solo a System.Threading.Timer.
Stonetip

i telefoni Windows probabilmente hanno una versione più leggera del framework, avere tutto quel codice extra per poter usare entrambi i metodi probabilmente non è necessario, quindi non è incluso. Penso che la risposta di Nick fornisca una ragione migliore per cui i telefoni Windows non hanno accesso System.Timers.Timerperché non gestirà le eccezioni lanciate ad esso.
Malachia

2

Sono d'accordo con il commento precedente che potrebbe essere meglio considerare un approccio diverso. Il mio suggerimento sarebbe scrivere un'applicazione console e utilizzare lo scheduler di Windows:

Questo sarà:

  • Riduci il codice dell'impianto idraulico che replica il comportamento dello scheduler
  • Fornire una maggiore flessibilità in termini di comportamento di pianificazione (ad esempio, eseguito solo nei fine settimana) con tutta la logica di pianificazione astratta dal codice dell'applicazione
  • Utilizza gli argomenti della riga di comando per i parametri senza dover impostare i valori di configurazione nei file di configurazione, ecc
  • Molto più facile eseguire il debug / test durante lo sviluppo
  • Consenti a un utente dell'assistenza di eseguire invocando direttamente l'applicazione della console (ad esempio utile durante le situazioni di supporto)

3
Ma questo richiede un utente connesso? Quindi il servizio è forse migliore se verrà eseguito 24 ore su 24, 7 giorni su 7 su un server.
JP Hellemons

1

Come già affermato sia System.Threading.Timere System.Timers.Timerfunzionerà. La grande differenza tra i due è che System.Threading.Timerè un involucro attorno all'altro.

System.Threading.Timeravrà più gestione delle eccezioni mentre System.Timers.Timeringoierà tutte le eccezioni.

Questo mi ha dato grossi problemi in passato, quindi usavo sempre "System.Threading.Timer" e gestivo ancora molto bene le tue eccezioni.


0

So che questo thread è un po 'vecchio ma è stato utile per uno scenario specifico che ho avuto e ho pensato che valesse la pena notare che c'è un altro motivo per cui System.Threading.Timerpotrebbe essere un buon approccio. Quando è necessario eseguire periodicamente un lavoro che potrebbe richiedere molto tempo e si desidera garantire che l'intero periodo di attesa venga utilizzato tra i lavori o se non si desidera che il lavoro venga eseguito di nuovo prima che il lavoro precedente sia terminato nel caso in cui il lavoro richiede più tempo del periodo del timer. Potresti usare quanto segue:

using System;
using System.ServiceProcess;
using System.Threading;

    public partial class TimerExampleService : ServiceBase
    {
        private AutoResetEvent AutoEventInstance { get; set; }
        private StatusChecker StatusCheckerInstance { get; set; }
        private Timer StateTimer { get; set; }
        public int TimerInterval { get; set; }

        public CaseIndexingService()
        {
            InitializeComponent();
            TimerInterval = 300000;
        }

        protected override void OnStart(string[] args)
        {
            AutoEventInstance = new AutoResetEvent(false);
            StatusCheckerInstance = new StatusChecker();

            // Create the delegate that invokes methods for the timer.
            TimerCallback timerDelegate =
                new TimerCallback(StatusCheckerInstance.CheckStatus);

            // Create a timer that signals the delegate to invoke 
            // 1.CheckStatus immediately, 
            // 2.Wait until the job is finished,
            // 3.then wait 5 minutes before executing again. 
            // 4.Repeat from point 2.
            Console.WriteLine("{0} Creating timer.\n",
                DateTime.Now.ToString("h:mm:ss.fff"));
            //Start Immediately but don't run again.
            StateTimer = new Timer(timerDelegate, AutoEventInstance, 0, Timeout.Infinite);
            while (StateTimer != null)
            {
                //Wait until the job is done
                AutoEventInstance.WaitOne();
                //Wait for 5 minutes before starting the job again.
                StateTimer.Change(TimerInterval, Timeout.Infinite);
            }
            //If the Job somehow takes longer than 5 minutes to complete then it wont matter because we will always wait another 5 minutes before running again.
        }

        protected override void OnStop()
        {
            StateTimer.Dispose();
        }
    }

    class StatusChecker
        {

            public StatusChecker()
            {
            }

            // This method is called by the timer delegate.
            public void CheckStatus(Object stateInfo)
            {
                AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
                Console.WriteLine("{0} Start Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                //This job takes time to run. For example purposes, I put a delay in here.
                int milliseconds = 5000;
                Thread.Sleep(milliseconds);
                //Job is now done running and the timer can now be reset to wait for the next interval
                Console.WriteLine("{0} Done Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                autoEvent.Set();
            }
        }
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.