Comment puis-je planifier un service Windows C # pour effectuer une tâche quotidiennement?


84

J'ai un service écrit en C # (.NET 1.1) et je souhaite qu'il effectue des actions de nettoyage à minuit tous les soirs. Je dois conserver tout le code contenu dans le service, alors quel est le moyen le plus simple d'y parvenir? Utilisation Thread.Sleep()et vérification du temps écoulé?


10
Vous voulez vraiment créer un processus .NET qui reste en mémoire, toute la journée, et fait quelque chose à minuit? Pourquoi ne pouvez-vous pas exactement, lors de l'installation de votre outil, configurer un événement système qui se déclenche à minuit (ou à une autre heure configurable) qui démarre votre application, fait son truc, puis disparaît?
Robert P

6
Je sais que je serais un peu énervé si je découvrais que j'avais un service géré consommant 20 à 40 Mo de mémoire, fonctionnant tout le temps, qui ne fait rien sauf une fois par jour. :)
Robert P

son lien
csa

Réponses:


86

Je n'utiliserais pas Thread.Sleep (). Soit utilisez une tâche planifiée (comme d'autres l'ont mentionné), soit configurez un minuteur dans votre service, qui se déclenche périodiquement (toutes les 10 minutes par exemple) et vérifiez si la date a changé depuis la dernière exécution:

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
Ne serait-il pas plus logique de simplement régler la minuterie pour qu'elle expire à minuit, au lieu de se réveiller toutes les minutes pour vérifier l'heure?
Kibbee le

11
@Kibbee: peut-être que si le service ne fonctionne pas à minuit (pour une raison quelconque), vous voudrez peut-être exécuter la tâche dès que le service est à nouveau en cours d'exécution.
M4N le

4
Ensuite, vous devez ajouter du code au démarrage du service pour déterminer si vous devez exécuter immédiatement, car le service ne fonctionnait pas, et il devrait l'être. Vous ne devriez pas vérifier toutes les 10 minutes en continu. Vérifiez si vous devez exécuter au démarrage, et si ce n'est pas le cas, déterminez la prochaine fois à exécuter. après cela, et réglez une minuterie pour la durée nécessaire.
Kibbee

3
@Kibbee: oui je suis d'accord (c'est un détail mineur dont nous discutons). Mais même si la minuterie se déclenche toutes les 10 secondes, cela ne ferait aucun mal (c'est-à-dire que cela ne prend pas beaucoup de temps).
M4N le

1
bonne solution mais le code ci-dessus est manquant _timer.start () et _lastRun doit être défini sur hier comme ceci: private DateTime _lastRun = DateTime.Now.AddDays (-1);
Zulu Z

72

Découvrez Quartz.NET . Vous pouvez l'utiliser dans un service Windows. Il vous permet d'exécuter un travail basé sur une planification configurée, et il prend même en charge une simple syntaxe de «tâche cron». J'ai eu beaucoup de succès avec ça.

Voici un exemple rapide de son utilisation:

// 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
Une bonne suggestion - dès que vous faites quelque chose d'encore légèrement plus complexe que le truc de base de la minuterie, vous devriez regarder Quartz.
serg10

2
Le quartz est si facile à utiliser, je dirais que même pour une tâche très simple, allez-y et utilisez-le. Cela donnera la possibilité d'ajuster facilement la planification.
Andy White

Super simple n'est-ce pas vraiment? La plupart des débutants attraperont ce problème ici stackoverflow.com/questions/24628372/…
Emil

21

Une tâche quotidienne? On dirait qu'il ne devrait s'agir que d'une tâche planifiée (panneau de contrôle) - pas besoin d'un service ici.


15

Doit-il s'agir d'un service réel? Pouvez-vous simplement utiliser les tâches planifiées intégrées dans le panneau de configuration de Windows.


Étant donné que le service existe déjà, il peut être plus facile d'y ajouter la fonctionnalité.
M4N le

1
La conversion d'un service à usage restreint comme celui-ci en une application console devrait être simple.
Stephen Martin

7
Il a déjà dit: "Je dois conserver tout le code contenu dans le service". Je ne pense pas qu'il soit nécessaire de remettre en question les exigences d'origine. Cela dépend, mais il existe parfois de très bonnes raisons d'utiliser un service sur une tâche planifiée.
jeremcc le

3
+1: Parce que vous devez toujours regarder vos problèmes de tous les côtés.
GvS

6
Si je n'avais vraiment qu'une seule tâche à exécuter par jour, alors je suppose qu'une simple application de console exécutée avec une tâche planifiée conviendrait. Mon point est que tout dépend de la situation, et dire "Ne pas utiliser un service Windows" n'est pas une réponse utile à l'OMI.
jeremcc le

3

La façon dont j'accomplis cela est avec une minuterie.

Exécutez une minuterie de serveur, demandez-lui de vérifier l'heure / minute toutes les 60 secondes.

Si c'est la bonne heure / minute, exécutez votre processus.

En fait, je l'ai résumé dans une classe de base que j'appelle OnceADayRunner.

Laissez-moi nettoyer un peu le code et je le posterai ici.

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

Le bœuf de la méthode est dans le contrôle e.SignalTime.Minute / Hour.

Il y a des crochets là-dedans pour les tests, etc.


Un léger retard dû à un serveur occupé pourrait lui faire manquer l'heure + la minute.
Jon B

1
Vrai. Quand j'ai fait cela, j'ai vérifié: l'heure est-elle maintenant supérieure à 00h00? Si tel est le cas, y a-t-il plus de 24 heures depuis que j'ai exécuté le travail pour la dernière fois? Si tel est le cas, exécutez le travail, sinon attendez à nouveau.
ZombieSheep le

2

Comme d'autres l'ont déjà écrit, une minuterie est la meilleure option dans le scénario que vous avez décrit.

En fonction de vos besoins exacts, la vérification de l'heure actuelle toutes les minutes peut ne pas être nécessaire. Si vous n'avez pas besoin d'effectuer l'action exactement à minuit, mais juste dans l'heure qui suit minuit, vous pouvez opter pour l'approche de Martin consistant à vérifier uniquement si la date a changé.

Si la raison pour laquelle vous souhaitez effectuer votre action à minuit est que vous vous attendez à une faible charge de travail sur votre ordinateur, mieux vaut faire attention: la même hypothèse est souvent faite par d'autres, et tout à coup vous avez 100 actions de nettoyage qui démarrent entre 0:00 et 0. : 01 h

Dans ce cas, vous devriez envisager de commencer votre nettoyage à un autre moment. Je fais généralement ces choses non pas à l'heure d'horloge, mais à une demi-heure (1,30 étant ma préférence personnelle)


1

Je vous suggère d'utiliser une minuterie, mais de la régler pour qu'elle vérifie toutes les 45 secondes, pas les minutes. Sinon, vous pouvez rencontrer des situations où, avec une charge lourde, la vérification d'une minute particulière est manquée, car entre le moment où le minuteur se déclenche et le moment où votre code s'exécute et vérifie l'heure actuelle, vous avez peut-être manqué la minute cible.


Si vous ne vérifiez pas qu'il n'a pas déjà été exécuté une fois, il est possible que vous atteigniez 00:00 deux fois si vous vérifiez jamais 45 secondes.
Michael Meadows le

Bon point. Ce problème peut être évité en appelant Sleep (15000) à la fin de la procédure de vérification. (ou peut-être 16000 ms, juste pour être sûr ;-)
Treb

Je suis d'accord, vous devriez vérifier s'il est déjà exécuté, quelle que soit la durée de la minuterie que vous choisissez.
GWLlosa le


0

Pour ceux qui ont trouvé que les solutions ci-dessus ne fonctionnaient pas, c'est parce que vous pouvez avoir un à l' thisintérieur de votre classe, ce qui implique une méthode d'extension qui, comme le message d'erreur l'indique, n'a de sens que sur une classe statique non générique. Votre classe n'est pas statique. Cela ne semble pas être quelque chose qui a du sens en tant que méthode d'extension, car il agit sur l'instance en question, supprimez donc le this.


-2

Essaye ça:

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
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.