Bester Timer für die Verwendung in einem Windows-Dienst

108

Ich muss einen Windows-Dienst erstellen, der alle N Zeiträume ausgeführt wird.
Die Frage ist:
Welche Timer-Steuerung soll ich verwenden: System.Timers.Timeroder System.Threading.Timereine? Beeinflusst es etwas?

Ich frage, weil ich viele Beweise für eine nicht korrekte Arbeit System.Timers.Timerin Windows-Diensten gehört habe.
Danke dir.

shA.t.
quelle

Antworten:

118

Beides System.Timers.Timerund System.Threading.Timerwird für Dienstleistungen arbeiten.

Die Timer, die Sie vermeiden möchten, sind System.Web.UI.Timerund System.Windows.Forms.Timer, die jeweils für ASP-Anwendungen und WinForms gelten. Wenn Sie diese verwenden, lädt der Dienst eine zusätzliche Assembly, die für den von Ihnen erstellten Anwendungstyp nicht wirklich benötigt wird.

Verwenden Sie diese Option System.Timers.Timerwie im folgenden Beispiel (stellen Sie außerdem sicher, dass Sie eine Variable auf Klassenebene verwenden, um die Speicherbereinigung zu verhindern, wie in der Antwort von Tim Robinson angegeben):

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
...
 */

Wenn Sie möchten System.Threading.Timer, können Sie Folgendes verwenden:

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

Beide Beispiele stammen von den MSDN-Seiten.

Andrew Moore
quelle
1
Warum schlagen Sie vor: GC.KeepAlive (aTimer);, aTimer ist die richtige Instanzvariable. Wenn es sich also beispielsweise um eine Instanzvariable der Form handelt, wird immer darauf verwiesen, solange es Form gibt, nicht wahr?
Giorgim
37

Verwenden Sie hierfür keinen Dienst. Erstellen Sie eine normale Anwendung und erstellen Sie eine geplante Aufgabe, um sie auszuführen.

Dies ist die gängige Best Practice. Jon Galloway stimmt mir zu. Oder vielleicht ist es umgekehrt. In beiden Fällen ist es nicht empfehlenswert, einen Windows-Dienst zu erstellen, um eine intermittierende Aufgabe auszuführen, die von einem Timer ausgeführt wird.

"Wenn Sie einen Windows-Dienst schreiben, auf dem ein Timer ausgeführt wird, sollten Sie Ihre Lösung neu bewerten."

–Jon Galloway, Community-Programmmanager von ASP.NET MVC, Autor, Teilzeit-Superheld

Gemeinschaft
quelle
26
Wenn Ihr Dienst den ganzen Tag ausgeführt werden soll, ist ein Dienst möglicherweise eher sinnvoll als eine geplante Aufgabe. Oder ein Dienst vereinfacht möglicherweise die Verwaltung und Protokollierung für eine Infrastrukturgruppe, die nicht so versiert ist wie das Anwendungsteam. Die Annahme, dass ein Service erforderlich ist, ist jedoch völlig gültig, und diese negativen Bewertungen sind unverdient. +1 für beide.
Sfuqua
2
@ Herr Unsinn. Geplante Aufgaben sind ein zentraler Bestandteil des Betriebssystems. Bitte behalten Sie Ihre FUD für sich.
4
@ MR: Ich bin kein Evangelist, ich bin ein Realist. Und die Realität hat mich gelehrt, dass geplante Aufgaben nicht "extrem fehlerhaft" sind. Es liegt in der Tat an Ihnen als der Person, die diesen Anspruch geltend gemacht hat, ihn zu unterstützen. Ansonsten verbreiten Sie nur Angst, Unsicherheit und Zweifel.
13
Ich hasse es, hier in den Sumpf zu waten, aber ich muss MR ein bisschen verteidigen. In meinem Unternehmen werden mehrere wichtige Anwendungen ausgeführt, bei denen es sich um Windows-Konsolenanwendungen handelt. Ich habe Windows Task Scheduler verwendet, um alle auszuführen. In mindestens fünf Fällen hatten wir ein Problem, bei dem der Scheduler-Dienst irgendwie "verwirrt" wurde. Aufgaben wurden nicht ausgeführt und einige befanden sich in einem seltsamen Zustand. Die einzige Lösung war ein Neustart des Servers oder das Stoppen und Starten des Scheduler-Dienstes. Nicht ohne Administratorrechte und in einer Produktionsumgebung nicht akzeptabel. Nur meine $ 0,02.
SpaceCowboy74
6
Es ist mir tatsächlich auf einigen Servern passiert (3 bisher). Ich werde nicht sagen, dass es die Norm ist. Nur zu sagen, manchmal ist es nicht schlecht, eine eigene Methode zu implementieren, um etwas zu tun.
SpaceCowboy74
7

Entweder sollte man OK arbeiten. Tatsächlich verwendet System.Threading.Timer intern System.Timers.Timer.

Trotzdem ist es leicht, System.Timers.Timer zu missbrauchen. Wenn Sie das Timer-Objekt nicht irgendwo in einer Variablen speichern, wird möglicherweise Müll gesammelt. In diesem Fall wird Ihr Timer nicht mehr ausgelöst. Rufen Sie die Dispose-Methode auf, um den Timer zu stoppen, oder verwenden Sie die System.Threading.Timer-Klasse, die ein etwas besserer Wrapper ist.

Welche Probleme haben Sie bisher gesehen?

Tim Robinson
quelle
Ich frage mich, warum eine Windows Phone-App nur auf System.Threading.Timer zugreifen kann.
Stonetip
Windows Phones haben wahrscheinlich eine leichtere Version des Frameworks. Der gesamte zusätzliche Code, um beide Methoden verwenden zu können, wird wahrscheinlich nicht benötigt und ist daher nicht enthalten. Ich denke, dass Nicks Antwort einen besseren Grund dafür liefert, warum Windows-Telefone keinen Zugriff haben, System.Timers.Timerweil sie die damit verbundenen Ausnahmen nicht verarbeiten.
Malachi
2

Ich stimme dem vorherigen Kommentar zu, der am besten geeignet ist, einen anderen Ansatz in Betracht zu ziehen. Mein Vorschlag wäre, eine Konsolenanwendung zu schreiben und den Windows-Scheduler zu verwenden:

Dieser Wille:

  • Reduzieren Sie den Installationscode, der das Scheduler-Verhalten repliziert
  • Bieten Sie mehr Flexibilität hinsichtlich des Planungsverhaltens (z. B. nur an Wochenenden), wobei die gesamte Planungslogik vom Anwendungscode abstrahiert wird
  • Verwenden Sie die Befehlszeilenargumente für Parameter, ohne Konfigurationswerte in Konfigurationsdateien usw. Einrichten zu müssen
  • Viel einfacher zu debuggen / testen während der Entwicklung
  • Ermöglichen Sie einem Support-Benutzer die Ausführung, indem Sie die Konsolenanwendung direkt aufrufen (z. B. nützlich in Support-Situationen).
user32378
quelle
3
Dies erfordert aber einen angemeldeten Benutzer? Der Service ist also vielleicht besser, wenn er rund um die Uhr auf einem Server ausgeführt wird.
JP Hellemons
1

Wie schon gesagt beides System.Threading.Timerund System.Timers.Timerwird funktionieren. Der große Unterschied zwischen den beiden besteht darin, dass System.Threading.Timeres sich um einen Wrapper um den anderen handelt.

System.Threading.Timerwird mehr Ausnahmebehandlung haben, während System.Timers.Timeralle Ausnahmen verschluckt werden.

Dies gab mir in der Vergangenheit große Probleme, so dass ich immer 'System.Threading.Timer' verwendete und Ihre Ausnahmen immer noch sehr gut handhabte.

Nick N.
quelle
0

Ich weiß, dass dieser Thread ein wenig alt ist, aber er hat sich für ein bestimmtes Szenario als nützlich erwiesen, und ich dachte, es lohnt sich zu bemerken, dass es einen weiteren Grund gibt, warum System.Threading.Timerein guter Ansatz sein könnte. Wenn Sie einen Job regelmäßig ausführen müssen, was lange dauern kann, und Sie sicherstellen möchten, dass die gesamte Wartezeit zwischen den Jobs verwendet wird, oder wenn Sie nicht möchten, dass der Job erneut ausgeführt wird, bevor der vorherige Job abgeschlossen ist, falls Der Job dauert länger als der Timer-Zeitraum. Sie könnten Folgendes verwenden:

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
quelle