web-dev-qa-db-fra.com

Meilleur minuteur pour utiliser un service Windows

J'ai besoin de créer un service Windows qui s'exécutera toutes les N périodes.
La question est:
Quel contrôle de minuterie devrais-je utiliser: System.Timers.Timer ou System.Threading.Timer un? Cela influence-t-il quelque chose? 

Je pose la question parce que j’ai entendu de nombreuses preuves d’un travail incorrect de System.Timers.Timer dans les services Windows.
Je vous remercie.

108
nKognito

Les deux System.Timers.Timer et System.Threading.Timer fonctionneront pour les services.

Les temporisations que vous souhaitez éviter sont System.Web.UI.Timer et System.Windows.Forms.Timer , qui correspondent respectivement aux applications ASP et à WinForm. L'utilisation de ceux-ci entraînera le service pour charger un assembly supplémentaire qui n'est pas vraiment nécessaire pour le type d'application que vous construisez.

Utilisez System.Timers.Timer comme dans l'exemple suivant (assurez-vous également que vous utilisez une variable de niveau classe pour empêcher la récupération de place, comme indiqué dans la réponse de 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
...
 */

Si vous choisissez System.Threading.Timer , vous pouvez utiliser comme suit:

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

Les deux exemples proviennent des pages MSDN.

118
Andrew Moore

N'utilisez pas un service pour cela. Créez une application normale et créez une tâche planifiée pour l'exécuter. 

C’est la meilleure pratique courante. Jon Galloway est d'accord avec moi. Ou peut-être que c'est l'inverse. Dans les deux cas, le fait est qu'il n'est pas recommandé de créer un service Windows pour exécuter une tâche intermittente à partir d'un minuteur.

"Si vous écrivez un service Windows qui exécute une minuterie, vous devez réévaluer votre solution."

–Jon Galloway, gestionnaire de programme de la communauté ASP.NET MVC, auteur, super-héros à temps partiel

37
Will

L'un ou l'autre devrait fonctionner correctement. En fait, System.Threading.Timer utilise System.Timers.Timer en interne.

Cela dit, il est facile d’abuser de System.Timers.Timer. Si vous ne stockez pas l'objet Timer dans une variable, il est susceptible d'être récupéré. Si cela se produit, votre minuterie ne se déclenchera plus. Appelez la méthode Dispose pour arrêter le minuteur ou utilisez la classe System.Threading.Timer, qui est un wrapper légèrement plus agréable.

Quels problèmes avez-vous vu jusqu'à présent?

7
Tim Robinson

Je suis d'accord avec le commentaire précédent qu'il pourrait être préférable d'envisager une approche différente. Ma suggestion serait d'écrire une application console et d'utiliser le planificateur Windows:

Cette volonté:

  • Réduire le code de plomberie qui réplique le comportement du planificateur
  • Offre une plus grande flexibilité en termes de comportement de planification (par exemple, uniquement run les fins de semaine) avec toute la logique de planification extraite du code de l'application
  • Utilisez les arguments de la ligne de commande Pour les paramètres sans avoir à Configurer les valeurs de configuration dans les fichiers config , Etc.
  • Beaucoup plus facile à déboguer/tester pendant le développement
  • Autoriser un utilisateur du support à s'exécuter en appelant L'application console directement (Utile par exemple lors de la prise en charge )
2
user32378

Comme déjà indiqué, System.Threading.Timer et System.Timers.Timer fonctionneront. La grande différence entre les deux est que System.Threading.Timer est une enveloppe autour de l'autre. 

System.Threading.Timer aura plus de gestion des exceptions tandis que System.Timers.Timer avalera toutes les exceptions.

Cela m'a donné de gros problèmes dans le passé, donc j'utilisais toujours 'System.Threading.Timer' et je traitais toujours très bien vos exceptions.

1
Nick N.

Je sais que ce fil est un peu vieux, mais il s’est avéré utile pour un scénario spécifique que j’avais eu et j’ai pensé qu’il était intéressant de noter qu’il existe une autre raison pour laquelle System.Threading.Timer pourrait être une bonne approche. Lorsque vous devez exécuter périodiquement un travail qui peut prendre beaucoup de temps et que vous voulez vous assurer que toute la période d'attente est utilisée entre les travaux ou si vous ne souhaitez pas que le travail soit réexécuté avant la fin du travail précédent le cas où le travail prend plus longtemps que la période du minuteur. Vous pouvez utiliser les éléments suivants:

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