Wie kann ich einen C # Windows-Dienst so planen, dass er täglich eine Aufgabe ausführt?

83

Ich habe einen Dienst in C # (.NET 1.1) geschrieben und möchte, dass er jeden Abend um Mitternacht einige Bereinigungsaktionen ausführt. Ich muss den gesamten im Service enthaltenen Code behalten. Was ist der einfachste Weg, dies zu erreichen? Verwendung Thread.Sleep()und Überprüfung der Zeitüberschreitung?

ctrlalt3nd
quelle
10
Sie möchten wirklich einen .NET-Prozess erstellen, der den ganzen Tag im Speicher bleibt und um Mitternacht etwas ausführt? Warum können Sie bei der Installation Ihres Tools nicht genau ein Systemereignis einrichten, das um Mitternacht (oder zu einer anderen konfigurierbaren Zeit) ausgelöst wird, das Ihre App startet, seine Aufgabe erfüllt und dann verschwindet?
Robert P
6
Ich weiß, ich wäre ein bisschen sauer, wenn ich herausfinden würde, dass ich einen verwalteten Dienst habe, der 20 bis 40 Megabyte Speicher verbraucht und ständig läuft und nichts anderes tut als einmal am Tag. :)
Robert P
sein duplizierter Link
csa

Antworten:

86

Ich würde Thread.Sleep () nicht verwenden. Verwenden Sie entweder eine geplante Aufgabe (wie bereits erwähnt) oder richten Sie in Ihrem Dienst einen Timer ein, der regelmäßig ausgelöst wird (z. B. alle 10 Minuten), und prüfen Sie, ob sich das Datum seit dem letzten Lauf geändert hat:

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();
    }
}
M4N
quelle
28
Wäre es nicht sinnvoller, den Timer so einzustellen, dass er um Mitternacht abläuft, anstatt jede Minute aufzuwachen, um die Uhrzeit zu überprüfen?
Kibbee
11
@Kibbee: Wenn der Dienst möglicherweise nicht um Mitternacht ausgeführt wird (aus welchem ​​Grund auch immer), möchten Sie die Aufgabe möglicherweise ausführen, sobald der Dienst erneut ausgeführt wird.
M4N
4
Dann sollten Sie beim Start des Dienstes Code hinzufügen, um herauszufinden, ob Sie sofort ausgeführt werden sollten, da der Dienst nicht ausgeführt wurde und dies auch sein sollte. Sie sollten nicht alle 10 Minuten kontinuierlich überprüfen. Überprüfen Sie, ob Sie beim Start ausgeführt werden sollen, und finden Sie dann heraus, ob Sie das nächste Mal ausführen müssen. Danach stellen Sie einen Timer so lange ein, wie Sie möchten.
Kibbee
3
@ Kibbee: Ja, ich stimme zu (es ist ein kleines Detail, das wir diskutieren). Aber selbst wenn der Timer alle 10 Sekunden ausgelöst wird, würde er keinen Schaden anrichten (dh es ist nicht zeitaufwändig).
M4N
1
Gute Lösung, aber der obige Code fehlt _timer.start () und _lastRun sollte wie folgt auf gestern gesetzt werden: private DateTime _lastRun = DateTime.Now.AddDays (-1);
Zulu Z
71

Schauen Sie sich Quartz.NET an . Sie können es in einem Windows-Dienst verwenden. Es ermöglicht Ihnen, einen Job basierend auf einem konfigurierten Zeitplan auszuführen, und es unterstützt sogar eine einfache "Cron-Job" -Syntax. Ich habe viel Erfolg damit gehabt.

Hier ist ein kurzes Beispiel für seine Verwendung:

// 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);
jeremcc
quelle
2
Ein guter Vorschlag - sobald Sie etwas tun, das noch etwas komplexer ist als das grundlegende Timer-Zeug, sollten Sie sich Quarz ansehen.
Serg10
2
Quarz ist so einfach zu bedienen, dass ich behaupten würde, dass Sie es selbst für eine supereinfache Aufgabe einfach verwenden sollten. Es gibt die Möglichkeit, die Planung einfach anzupassen.
Andy White
Super einfach ist das nicht wirklich? Die meisten Neulinge werden dieses Problem hier abfangen stackoverflow.com/questions/24628372/…
batmaci
21

Eine tägliche Aufgabe? Klingt so, als ob es sich nur um eine geplante Aufgabe (Systemsteuerung) handeln sollte - hier ist kein Service erforderlich.

Marc Gravell
quelle
15

Muss es ein tatsächlicher Service sein? Können Sie einfach die eingebauten geplanten Aufgaben in der Windows-Systemsteuerung verwenden?

Ian Jacobs
quelle
Da der Dienst bereits vorhanden ist, ist es möglicherweise einfacher, die Funktionalität dort hinzuzufügen.
M4N
1
Das Konvertieren eines solchen Dienstes mit engem Zweck in eine Konsolen-App sollte trivial sein.
Stephen Martin
7
Er sagte bereits: "Ich muss den gesamten im Service enthaltenen Code behalten." Ich halte es nicht für notwendig, die ursprünglichen Anforderungen in Frage zu stellen. Es hängt davon ab, aber manchmal gibt es sehr gute Gründe, einen Dienst für eine geplante Aufgabe zu verwenden.
Jeremcc
3
+1: Weil Sie Ihre Probleme immer von allen Seiten betrachten müssen.
GvS
6
Wenn ich wirklich nur eine Aufgabe pro Tag ausführen müsste, wäre eine einfache Konsolen-App mit einer geplanten Aufgabe in Ordnung. Mein Punkt ist, dass alles von der Situation abhängt und die Aussage "Verwenden Sie keinen Windows-Dienst" keine nützliche Antwort ist, IMO.
Jeremcc
3

Ich erreiche dies mit einem Timer.

Führen Sie einen Server-Timer aus und überprüfen Sie alle 60 Sekunden die Stunde / Minute.

Wenn es die richtige Stunde / Minute ist, führen Sie Ihren Prozess aus.

Ich habe dies tatsächlich in eine Basisklasse abstrahiert, die ich OnceADayRunner nenne.

Lassen Sie mich den Code ein wenig aufräumen und ich werde ihn hier posten.

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

Das Rindfleisch der Methode befindet sich in der e.SignalTime.Minute / Hour-Prüfung.

Es gibt Haken zum Testen usw., aber so könnte Ihr verstrichener Timer aussehen, damit alles funktioniert.

CubanX
quelle
Eine leichte Verzögerung aufgrund eines ausgelasteten Servers kann dazu führen, dass die Stunde + Minute verpasst wird.
Jon B
1
Wahr. Als ich das tat, überprüfte ich: Ist die Zeit jetzt größer als 00:00? Wenn ja, sind mehr als 24 Stunden vergangen, seit ich den Job das letzte Mal ausgeführt habe? Wenn ja, führen Sie den Job aus, andernfalls warten Sie erneut.
ZombieSheep
2

Wie andere bereits geschrieben haben, ist ein Timer die beste Option in dem von Ihnen beschriebenen Szenario.

Abhängig von Ihren genauen Anforderungen ist es möglicherweise nicht erforderlich, die aktuelle Zeit jede Minute zu überprüfen. Wenn Sie die Aktion nicht genau um Mitternacht, sondern nur innerhalb einer Stunde nach Mitternacht ausführen müssen , können Sie nach Martins Ansatz nur prüfen, ob sich das Datum geändert hat.

Wenn der Grund, warum Sie Ihre Aktion um Mitternacht ausführen möchten, darin besteht, dass Sie eine geringe Arbeitslast auf Ihrem Computer erwarten, sollten Sie besser darauf achten: Dieselbe Annahme wird häufig von anderen getroffen, und plötzlich werden zwischen 0:00 und 0 100 Bereinigungsaktionen gestartet : 01 Uhr

In diesem Fall sollten Sie in Betracht ziehen, die Bereinigung zu einem anderen Zeitpunkt zu starten. Normalerweise mache ich diese Dinge nicht zur vollen Stunde, sondern zur halben Stunde (1.30 Uhr ist meine persönliche Präferenz)

Treb
quelle
1

Ich würde vorschlagen, dass Sie einen Timer verwenden, diesen jedoch so einstellen, dass er alle 45 Sekunden und nicht jede Minute überprüft wird. Andernfalls kann es zu Situationen kommen, in denen bei starker Belastung die Prüfung für eine bestimmte Minute verpasst wird, da Sie zwischen dem Auslösen des Timers und der Ausführung Ihres Codes und dem Überprüfen der aktuellen Zeit möglicherweise die Zielminute verpasst haben.

GWLlosa
quelle
Wenn Sie nicht überprüfen, ob es noch nicht einmal ausgeführt wurde, besteht die Möglichkeit, dass Sie zweimal 00:00 drücken, wenn Sie alle 45 Sekunden überprüfen.
Michael Meadows
Guter Punkt. Dieses Problem kann vermieden werden, indem am Ende des Überprüfungsvorgangs Sleep (15000) aufgerufen wird. (oder vielleicht 16000 ms, nur um auf der sicheren Seite zu sein ;-)
Treb
Ich bin damit einverstanden, Sie sollten überprüfen, ob es bereits ausgeführt wird, unabhängig davon, welche Timer-Dauer Sie auswählen.
GWLlosa
0

Für diejenigen, die festgestellt haben, dass die oben genannten Lösungen nicht funktionieren, liegt dies daran, dass Sie möglicherweise eine thisin Ihrer Klasse haben, was eine Erweiterungsmethode impliziert, die, wie in der Fehlermeldung angegeben, nur für eine nicht generische statische Klasse sinnvoll ist. Ihre Klasse ist nicht statisch. Dies scheint als Erweiterungsmethode nicht sinnvoll zu sein, da es auf die betreffende Instanz wirkt. Entfernen Sie daher die this.

Fandango68
quelle
-2

Versuche dies:

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
    }
}
Shailendra Tiwari
quelle