Miglior timer per l’utilizzo in un servizio di Windows

Devo creare un servizio windows che esegua ogni N periodo di tempo.
La domanda è:
Quale controllo timer dovrei usare: System.Timers.Timer o System.Threading.Timer uno? Influenza su qualcosa?

Ti sto chiedendo perché ho sentito molte prove sul lavoro non corretto di System.Timers.Timer nei servizi di Windows.
Grazie.

Sia System.Timers.Timer che System.Threading.Timer funzioneranno per i servizi.

I timer che si desidera evitare sono System.Web.UI.Timer e System.Windows.Forms.Timer , che sono rispettivamente per applicazioni ASP e WinForms. L’utilizzo di questi consentirà al servizio di caricare un assembly aggiuntivo che non è realmente necessario per il tipo di applicazione che si sta creando.

Usa System.Timers.Timer come nell’esempio seguente (inoltre, assicurati di utilizzare una variabile di livello di class per impedire 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.

Non usare un servizio per questo. Creare un’applicazione normale e creare un’attività programmata per eseguirla.

Questa è la best practice comunemente tenuta. Jon Galloway è d’accordo con me. O forse è il contrario. In entrambi i casi, il fatto è che non è consigliabile creare un servizio Windows per eseguire un’attività intermittente con un timer.

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

-Jon Galloway, responsabile del programma di comunità ASP.NET MVC, autore, supereroe part time

O si dovrebbe funzionare OK. In effetti, System.Threading.Timer utilizza internamente System.Timers.Timer.

Detto questo, è facile abusare di System.Timers.Timer. Se non si memorizza l’object Timer in una variabile da qualche parte, è ansible che venga eseguito il Garbage Collection. Se ciò accade, il timer non sparerà più. Chiama il metodo Dispose per interrompere il timer o usa la class System.Threading.Timer, che è un wrapper leggermente più bello.

Quali problemi hai visto finora?

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 idraulico che replica il comportamento dello scheduler
  • Fornire una maggiore flessibilità in termini di comportamento di pianificazione (ad esempio, solo nei fine settimana) con tutte le logiche di pianificazione astratte dal codice dell’applicazione
  • Utilizzare 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
  • Consentire a un utente di supporto di eseguire richiamando direttamente l’applicazione console (ad es. Utile durante le situazioni di supporto)

Come già affermato, System.Threading.Timer e System.Timers.Timer funzioneranno. La grande differenza tra i due è che System.Threading.Timer è un wrapper attorno all’altro.

System.Threading.Timer avrà più gestione delle eccezioni mentre System.Timers.Timer ingerirà tutte le eccezioni.

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

So che questo thread è un po ‘vecchio ma è stato utile per uno specifico scenario che ho avuto e ho pensato che valesse la pena notare che c’è un altro motivo per cui System.Threading.Timer potrebbe essere un buon approccio. Quando si deve eseguire periodicamente un lavoro che potrebbe richiedere molto tempo e si desidera assicurarsi che l’intero periodo di attesa venga utilizzato tra i lavori o se non si desidera che il lavoro venga eseguito nuovamente prima del completamento del lavoro precedente 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(); } }