web-dev-qa-db-ger.com

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

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

Ich frage, weil ich viele Hinweise auf nicht korrekte Arbeit von System.Timers.Timer in Windows-Diensten gehört habe.
Vielen Dank.

108
nKognito

Beide System.Timers.Timer und System.Threading.Timer funktionieren für Dienste.

Die Timer, die Sie vermeiden möchten, sind System.Web.UI.Timer und 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 System.Timers.Timer wie im folgenden Beispiel (stellen Sie außerdem sicher, dass Sie eine Klassenebenenvariable 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 System.Threading.Timer wählen, 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.

118
Andrew Moore

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

Dies ist die allgemein bewährte Praxis. Jon Galloway stimmt mir zu. Oder vielleicht ist es andersherum. In jedem Fall ist es eine Tatsache, dass es nicht empfohlen wird, einen Windows-Dienst zu erstellen, um eine zeitweilige Task auszuführen, bei der ein Timer ausgeführt wird.

"Wenn Sie einen Windows-Dienst schreiben, der einen Timer ausführt, sollten Sie Ihre Lösung erneut bewerten."

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

37
Will

Entweder sollte man gut funktionieren. Tatsächlich verwendet System.Threading.Timer intern System.Timers.Timer.

Trotzdem ist es einfach, System.Timers.Timer zu missbrauchen. Wenn Sie das Timer-Objekt nicht irgendwo in einer Variablen speichern, besteht die Gefahr, dass Müll gesammelt wird. 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 Klasse System.Threading.Timer, die einen etwas schöneren Wrapper darstellt.

Welche Probleme hast du bisher gesehen?

7
Tim Robinson

Ich stimme dem vorherigen Kommentar zu, der sich am besten für einen anderen Ansatz eignet. 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 eine größere Flexibilität in Bezug auf .__ des Ablaufverhaltens (z. B. nurrun an Wochenenden), wobei die gesamte Planungslogik vom Anwendungscode abstrahiert wird
  • Verwenden Sie die Befehlszeilenargumente Für Parameter, ohne Konfigurationswerte in Konfig -Dateien usw. Einrichten zu müssen
  • Das Debuggen/Testen während der Entwicklung ist viel einfacher
  • Erlauben Sie einem Support-Benutzer die Ausführung durch direktes Aufrufen von Der Konsolenanwendung (Z. B. nützlich während der Unterstützung Situationen)
2
user32378

Wie bereits erwähnt, funktionieren sowohl System.Threading.Timer als auch System.Timers.Timer. Der große Unterschied zwischen den beiden ist, dass System.Threading.Timer ein Wrapper um den anderen ist. 

System.Threading.Timer hat mehr Ausnahmebehandlung, während System.Timers.Timer schluckt alle Ausnahmen.

Dies gab mir in der Vergangenheit große Probleme, daher würde ich immer 'System.Threading.Timer' verwenden und trotzdem mit Ihren Ausnahmen sehr gut umgehen.

1
Nick N.

Ich weiß, dass dieser Thread ein wenig alt ist, aber für ein bestimmtes Szenario, das ich hatte, sehr praktisch war, und ich dachte, es lohnt sich zu beachten, dass es einen anderen Grund gibt, warum System.Threading.Timer ein guter Ansatz ist. Wenn Sie regelmäßig einen Job ausführen müssen, der möglicherweise lange Zeit in Anspruch nimmt, und Sie möchten sicherstellen, 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 der Fall, in dem der Job länger als die Zeitspanne dauert. 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();
            }
        }
0
MigaelM