Meilleur minuteur pour une utilisation dans un service Windows

108

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

Je demande parce que j'ai entendu de nombreuses preuves de travaux non corrects System.Timers.Timerdans les services Windows.
Je vous remercie.

shA.t
la source

Réponses:

118

Les deux System.Timers.Timeret System.Threading.Timertravailleront pour les services.

Les minuteries que vous souhaitez éviter sont System.Web.UI.Timeret System.Windows.Forms.Timer, qui sont respectivement pour les applications ASP et WinForms. Leur utilisation entraînera le chargement d'un assemblage supplémentaire par le service qui n'est pas vraiment nécessaire pour le type d'application que vous créez.

Utilisez System.Timers.Timercomme l'exemple suivant (assurez-vous également d'utiliser une variable de niveau de classe pour empêcher le garbage collection, 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 le souhaitez 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.

Andrew Moore
la source
1
Pourquoi suggérez-vous: GC.KeepAlive (aTimer) ;, aTimer est une variable d'instance à droite, donc par exemple, s'il s'agit d'une variable d'instance de forme, il y aura toujours référence tant qu'il y a forme, n'est-ce pas?
giorgim
37

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

Il s'agit de la meilleure pratique courante. Jon Galloway est d'accord avec moi. Ou peut-être que c'est l'inverse. Quoi qu'il en soit, le fait est qu'il n'est pas recommandé de créer un service Windows pour effectuer une tâche intermittente exécutée sur un minuteur.

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

–Jon Galloway, responsable du programme communautaire ASP.NET MVC, auteur, super-héros à temps partiel

Communauté
la source
26
Si votre service est destiné à fonctionner toute la journée, alors peut-être qu'un service a du sens plutôt qu'une tâche planifiée. Ou, avoir un service peut simplifier l'administration et la journalisation pour un groupe d'infrastructure qui n'est pas aussi avisé que l'équipe d'application. Cependant, remettre en question l'hypothèse qu'un service est requis est tout à fait valable, et ces évaluations négatives ne sont pas méritées. +1 aux deux.
sfuqua
2
@mr Nonsense. Les tâches planifiées font partie intégrante du système d'exploitation. Veuillez garder votre FUD pour vous.
4
@MR: Je ne suis pas un évangéliste, je suis réaliste. Et la réalité m'a appris que les tâches planifiées ne sont pas «extrêmement boguées». En fait, c'est à vous, en tant que personne qui a fait cette demande, de l'appuyer. Sinon, vous ne faites que semer la peur, l'incertitude et le doute.
13
Je déteste patauger dans la boue ici, mais je dois défendre un peu MR. J'ai plusieurs applications critiques en cours d'exécution dans mon entreprise qui sont des applications de console Windows. J'ai utilisé le planificateur de tâches Windows pour tous les exécuter. Au moins 5 fois maintenant, nous avons eu un problème où le service de planification "s'est confus" d'une manière ou d'une autre. Les tâches ne s'exécutaient pas et certaines étaient dans un état étrange. La seule solution était un redémarrage du serveur ou l'arrêt et le démarrage du service Scheduler. Ce n'est pas quelque chose que je peux faire sans droits d'administrateur et qui n'est pas acceptable dans un environnement de production. Juste mon 0,02 $.
SpaceCowboy74
6
Cela m'est effectivement arrivé sur quelques serveurs (3 jusqu'à présent). Je ne dirai pas que c'est la norme cependant. Il suffit de dire que parfois, ce n'est pas mal d'implémenter votre propre méthode pour faire quelque chose.
SpaceCowboy74
7

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

Cela dit, il est facile de mal utiliser System.Timers.Timer. Si vous ne stockez pas l'objet Timer quelque part 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 vus jusqu'à présent?

Tim Robinson
la source
Je me demande pourquoi une application Windows Phone ne peut accéder qu'à System.Threading.Timer.
Stonetip
les téléphones Windows ont probablement une version plus légère du framework, avoir tout ce code supplémentaire pour pouvoir utiliser les deux méthodes n'est probablement pas nécessaire, il n'est donc pas inclus. Je pense que la réponse de Nick donne une meilleure raison pour laquelle les téléphones Windows n'y ont pas accès System.Timers.Timerparce qu'il ne gérera pas les exceptions qui lui sont lancées.
Malachi
2

Je suis d'accord avec le commentaire précédent qu'il serait peut-ê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éduisez le code de plomberie qui réplique le comportement du planificateur
  • Fournit une plus grande flexibilité en termes de comportement de planification (par exemple, ne fonctionne que le week-end) avec toute la logique de planification abstraite du code de l'application
  • Utilisez les arguments de ligne de commande pour les paramètres sans avoir à configurer les valeurs de configuration dans les fichiers de configuration, etc.
  • Beaucoup plus facile à déboguer / tester pendant le développement
  • Permettre à un utilisateur d'assistance de s'exécuter en appelant directement l'application console (par exemple utile lors de situations d'assistance)
user32378
la source
3
Mais cela nécessite un utilisateur connecté? Le service est donc peut-être meilleur s'il fonctionne 24h / 24 et 7j / 7 sur un serveur.
JP Hellemons
1

Comme déjà indiqué à la fois System.Threading.Timeret System.Timers.Timerfonctionnera. La grande différence entre les deux est qu'il System.Threading.Timery a un wrapper autour de l'autre.

System.Threading.Timeraura plus de gestion des exceptions tandis que System.Timers.Timeravalera toutes les exceptions.

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

Nick N.
la source
0

Je sais que ce fil est un peu vieux, mais il m'a été utile pour un scénario spécifique que j'avais et j'ai pensé qu'il valait la peine de noter qu'il y a une autre raison pour laquelle System.Threading.Timerune bonne approche pourrait être. Lorsque vous devez exécuter périodiquement un Job qui peut prendre du temps et que vous voulez vous assurer que toute la période d'attente est utilisée entre les jobs ou si vous ne voulez pas que le job soit à nouveau exécuté avant la fin du job précédent dans le cas où le travail prend plus de temps 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();
            }
        }
MigaelM
la source