Wenn man einen Windows Service für die Erledigung von Aufgaben bereitstellen möchte und den Job beispielsweise jede Nacht um 3 Uhr erledigen muss, sollte man einen Cronjob / Scheduler dazu verwenden.

Aufbau Crontab

* * * * * - - - - - | | | | | | | | | +----- day of week (0 - 6) (Sunday=0) | | | +------- month (1 - 12) | | +--------- day of month (1 - 31) | +----------- hour (0 - 23) +------------- min (0 - 59)

Um aus diesem Format ein Datum zu bekommen, verwende ich die Bibliothek NCrontab. Hiermit ist es mir möglich, aus solch einem Crontab ein Datum bzw. eine Liste der nächsten Terminen / Jobs zu erhalten.

Eine Liste von Terminen, bekommt man mit der Methode GetNextOccurrences().

CrontabSchedule s = CrontabSchedule.Parse("*/5 * * * *"); var dates = s.GetNextOccurrences(DateTime.Now, DateTime.Now.AddHours(1));

Und den nächsten Termin mit der Methode GetNextOccurrence()

CrontabSchedule s = CrontabSchedule.Parse("*/5 * * * *"); var next = s.GetNextOccurrence(DateTime.Now);

Mit der System.Threading.Timer Klasse kann ich diesen Cronjob ausführen. Ein einfaches Konsolenbeispiel sähe so aus:

static void Main(string[] args) { CrontabSchedule s = CrontabSchedule.Parse("*/5 * * * *"); var next = s.GetNextOccurrence(DateTime.Now); var callback = new System.Threading.TimerCallback(TimerElapsed); new System.Threading.Timer(callback, null, next - DateTime.Now, next - DateTime.Now); Console.ReadLine(); } private static void TimerElapsed(object state) { //do things }

Hier führe ich im Beispiel den Job alle 5 Minuten aus. Das heißt meine Methode TimerElapsed wird alle 5 Minuten aufgerufen.

Klar, dieses Beispiel könnte man sicher mit einem normalen Timer erledigen, jedoch wenn man komplexe Zeiten einsetzen muss, wird das schon schwieriger. Mit einem Crontab kann ich sagen “Erledige den Job 3 mal im Monat, einmal vormittags und einmal nachmittags.

Ein paar Beispiele, wie man ein Crontab erstellt findet man hier.

Okay, soweit so gut. Nun heißt der Titel des Beitrags aber “Cronjob im Windows Service”. Ich schreibe diesen deshalb, weil ich mich wunderte, dass der funktionierende Konsolencode nicht in meinem Windows Service funktionierte. Bzw. der Job wurde lediglich ein einziges Mal ausgeführt.

Der Grund dafür ist, das der GarbageCollector die Timer Referenz, die ich in der OnStart-Methode erstellt habe, eliminiert hat.

Um dennoch das o.g. Konstrukt zu verwenden, muss man lediglich eine globale Timer Variable halten.

Das ganze sieht dann so aus:

using System; using System.Configuration; using System.Diagnostics; using System.ServiceProcess; using System.Threading; using NCrontab; ... public partial class MyService : ServiceBase { TimerCallback _callback; private Timer _timer; public MyService() { InitializeComponent(); } readonly CrontabSchedule _scheduler = CrontabSchedule.Parse(ConfigurationManager.AppSettings["cronjob"]); DateTime _next; void TimerElapsed(object sender) { try { //do things } catch (Exception ex) { //.. throw; } } protected override void OnStart(string[] args) { _callback = (TimerElapsed); _next = _scheduler.GetNextOccurrence(DateTime.Now); _timer = new System.Threading.Timer(_callback, null, _next - DateTime.Now, _next - DateTime.Now); } protected override void OnStop() { } }

Die Konfiguration dazu:

<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="cronjob" value="*/5 * * * *" /> </appSettings> </configuration>

Viel Spaß beim entwickeln : )

Cronjob im Windows Service
Markiert in: