37 Stimmen

Windows-Dienst System.Timers.Timer wird nicht ausgelöst

Ich habe einen in C# geschriebenen Windows-Dienst, der alle paar Minuten eine Aufgabe ausführen soll. Ich verwende eine System.Timers.Timer aber es scheint nicht zu funktionieren. Ich habe nachgeschaut viele verschiedene Beiträge hier auf SO und anderswo und ich sehe nicht, was mit meinem Code falsch ist.

Hier ist mein Code, wobei nicht zeitabhängige Elemente aus Gründen der Übersichtlichkeit entfernt wurden...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Timers.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
            IntervalTimer = new System.Timers.Timer(60000);  // Default in case app.config is silent.
            IntervalTimer.Enabled = false;
            IntervalTimer.Elapsed += new ElapsedEventHandler(this.IntervalTimer_Elapsed);
        }

        protected override void OnStart(string[] args)
        {
            // Set up the timer...
            IntervalTimer.Enabled = false;
            IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
            // Start the timer and wait for the next work to be released...
            IntervalTimer.Start();
        }

        protected override void OnStop()
        {
            IntervalTimer.Enabled = false;
        }

        private void IntervalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {   // Do the thing that needs doing every few minutes...
            DoWork();
        }
    }
}

Ich kratze mir wirklich den Kopf darüber. Kann mir jemand sagen, was ich hier falsch mache?

EDITAR: Auf Anregung habe ich Folgendes hinzugefügt IntervalTimer.Enabled = true; vor IntervalTimer.Start(); in der OnStart-Methode des Dienstes. Damit ist das Problem nicht gelöst.

Ich habe Datei-Trace-Protokollierung in den Dienst hinzugefügt, um einige der Interna zu bestätigen, und ich weiß sicher, dass der Timer.Enabled-Wert wahr ist, wenn OnStart() beendet ist.

57voto

Joel Brown Punkte 13792

Hier ist meine Abhilfe...

Nachdem ich viel zu viele Stunden nach einer Antwort auf diese Frage gesucht habe, bin ich auf eine Vielzahl von Artikeln und Blogs gestoßen, in denen Timer in Windows-Diensten diskutiert werden. Ich habe eine Los Dazu gibt es verschiedene Meinungen, die sich alle in drei Kategorien und in absteigender Reihenfolge der Häufigkeit einordnen lassen:

  1. Verwenden Sie nicht System.Windows.Forms.Timer weil es nicht funktionieren wird. (das macht nur Sinn)

  2. Verwenden Sie nicht System.Threading.Timer weil es nicht funktioniert, verwenden Sie System.Timers.Timer stattdessen.

  3. Verwenden Sie nicht System.Timers.Timer weil es nicht funktioniert, verwenden Sie System.Threading.Timer stattdessen.

Daraufhin versuchte ich es mit 2. Dies ist auch der Ansatz, der von Microsoft empfohlen zu werden scheint, da sie sagen, dass System.Timers.Timer ist geeignet für "Serveranwendungen" .

Ich habe festgestellt, dass System.Timers.Timer funktioniert einfach nicht in meiner Windows Service Anwendung. Deshalb bin ich umgestiegen auf System.Threading.Timer . Es ist ein Ärgernis, da es einige Umstrukturierungen erfordert, damit es funktioniert.

So sieht mein Arbeitscode jetzt ungefähr aus...

namespace NovaNotificationService
{
    public partial class NovaNotificationService : ServiceBase
    {
        private System.Threading.Timer IntervalTimer;
        public NovaNotificationService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            TimeSpan tsInterval = new TimeSpan(0, 0, Properties.Settings.Default.PollingFreqInSec);
            IntervalTimer = new System.Threading.Timer(
                new System.Threading.TimerCallback(IntervalTimer_Elapsed)
                , null, tsInterval, tsInterval);
        }

        protected override void OnStop()
        {
            IntervalTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            IntervalTimer.Dispose();
            IntervalTimer = null;
        }

        private void IntervalTimer_Elapsed(object state)
        {   // Do the thing that needs doing every few minutes...
            // (Omitted for simplicity is sentinel logic to prevent re-entering
            //  DoWork() if the previous "tick" has for some reason not completed.)
            DoWork();
        }
    }
}

Ich hasse die "Doktor, Doktor, es tut weh, wenn ich das mache..."-Lösung, aber das war es, was ich tun musste. Eine weitere Meinung auf dem Stapel für den nächsten Kerl mit diesem Problem...

10voto

Michał Powaga Punkte 21502

Sie vergessen Timer aktivieren durch Einstellung:

IntervalTimer.Enabled = true;

oder Aufruf Start Methode :

IntervalTimer.Start();

protected override void OnStart(string[] args)
{
    // Set up the timer...
    IntervalTimer.Interval = Properties.Settings.Default.PollingFreqInSec * 1000;
    // Start the timer and wait for the next work to be released...
    IntervalTimer.Start();
}

8voto

Peter Huppertz Punkte 351

Offenbar versteckt System.Timers.Timer alle Ausnahmen, schluckt sie still und leise und verschluckt sich dann. Natürlich können Sie diese in Ihrer Methode behandeln, die Sie als Handler zu Ihrem Timer hinzugefügt haben, aber wenn die Ausnahme sofort beim Eintritt ausgelöst wird (bevor die erste Codezeile ausgeführt wird, was z. B. passieren kann, wenn Ihre Methode eine Variable deklariert, die ein Objekt in einer stark benannten DLL verwendet, von der Sie die falsche Version haben), werden Sie diese Ausnahme nie sehen.

Und Sie werden sich wie wir alle die Haare raufen.

Oder Sie könnten dies tun:

  • eine Wrapper-Methode erstellen, die (in einer try-catch-Schleife) die Methode aufruft, die Sie ausführen lassen möchten. Wenn diese Methode bei Ihnen abstirbt, kann die Wrapper-Methode die Ausnahmebehandlung übernehmen, ohne den Zeitgeber zu beenden, denn wenn Sie den Zeitgeber nicht anhalten, wird er nie merken, dass etwas schief gelaufen ist.

(Am Ende habe ich den Timer gestoppt, denn wenn er fehlschlägt, macht ein erneuter Versuch für diese spezielle Anwendung keinen Sinn...)

Ich hoffe, das hilft denen, die über Google hier gelandet sind (so wie ich).

5voto

AUR Punkte 613

Ich musste auch zu System.Threading.Timer wechseln. Um das Re-Factoring zu vereinfachen und anderen das Leben zu erleichtern, habe ich eine separate Klasse erstellt, die eine Instanz von System.Threading.Timer enthält und fast die gleichen Methoden wie System.Timers.Timer hat, so dass der aufrufende Code nur minimale Änderungen erfordert:

/// <summary>
/// Custom Timer class, that is actually a wrapper over System.Threading.Timer
/// </summary>
/// <seealso cref="System.IDisposable" />
internal class Timer : IDisposable
{
    System.Threading.Timer _timer;

    public Timer()
    {

    }
    public Timer(int interval) : this()
    {
        this.Interval = interval;
    }

    public bool AutoReset { get; set; }
    public bool Enabled { get; set; }
    public int Interval { get; set; }
    public Action<object> OnTimer { get; internal set; }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            _timer.Dispose();
            _timer = null;
        }
    }

    public void Start()
    {
        _timer = new System.Threading.Timer(
            new System.Threading.TimerCallback(OnTimer), null, 0, Interval);
    }
    public void Stop()
    {
        if (_timer != null)
        {
            _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        }
    }
}

Ich hoffe, das wird helfen!

4voto

user3224303 Punkte 71

Um zu ergänzen, was "user1820848" geschrieben hat, weil das auch mein Problem war, wenn Ihr System.timers.timer elapsed-Ereignis nicht ausgelöst zu werden scheint, setzen Sie alles im Event-Handler in einem try/catch-Block und suchen Sie dort nach Problemen. Ich habe alle empfohlenen Methoden ausprobiert, um dieses Problem zu lösen (oder dachte, ich hätte es), einschließlich des Wechsels von system.timers.timer zu system.threading.timer, und auch das hat nicht funktioniert.

Ich denke, das Problem wird dadurch verschärft, dass viele von uns ihre Anwendungen von ihrer Workstation, wo wir uns an den laufenden Dienst anhängen und überprüfen können, ob er funktioniert, auf einen Server verlagern, wo wir keine Debugging-Unterstützung haben. Man ist also mit Ereignisprotokollmeldungen oder Tracelistener-Meldungen konfrontiert, und es ist völlig merkwürdig, dass das Ereignis nicht ausgelöst wird.

Ich hatte eine Situation, in der ich drei laufende Dienste auf diesem Server habe, die im Wesentlichen den gleichen Zeitgebercode ausführen. Ich bin sogar Zeile für Zeile mit dem Code eines anderen laufenden Dienstes durchgegangen, um sicherzustellen, dass ich die system.timers.timer-Behandlung gleich mache. Aber der andere Dienst funktioniert gut, und dieser schien das Ereignis überhaupt nicht auszulösen.

Das Problem war, wie sich herausstellte, dass ich in meinen anfänglichen Dim-Anweisungen eine Klasse startete, die versuchte, eine Verbindung zu Oracle herzustellen. Dieser Aufruf schlug fehl, aber er schlug tatsächlich fehl, weil die Oracle-Client-Version auf meiner Workstation und dem Server leicht anders. Es geschah, als die CLR die Verweise auflöste, also wurde es nicht in meiner zugrunde liegenden Klasse Try/Catch-Blöcke gefangen. Wenn ich debuggen würde, würde der Debugger den Fehler markiert haben. Beim Ausführen auf dem Server hatte die CLR keine Möglichkeit, mich über das Problem zu informieren. Also mein Dienst saß nur dort auf eine untrapped Fehler.

Putting alles in einem try/catch wies sofort auf das Problem hin. Setzen Sie Ihr try vor alle Deklarationen in dieser Subroutine. Wenn Sie an einer sehr frühen Anweisung scheitern, werden Sie es auf diese Weise feststellen.

(Entschuldigung für die separate Antwort, aber man muss Antworten geben, um genug Ansehen zu bekommen, um die Antwort eines anderen zu kommentieren?]

[Bearbeiten: eine andere Sache zu versuchen ist, nehmen Sie Ihren Code aus dem Timer-Ereignis, legen Sie es in eine andere Sub/Funktion, rufen Sie, dass von Ihrem Start-Code, und auch die Funktion in Ihrem Timer-Ereignis aufrufen. Wochen später sitze ich wieder an meinem Arbeitsplatz und versuche, denselben Code auszuführen, und habe das ungute Gefühl, dass mein Timer-Ereignis nicht aufgerufen wird, und ich war schon einmal hier. Stimmt! Aber alles in ein try/catch zu packen, funktioniert auch nicht!? Ich habe es in einen Funktionsaufruf verschoben und bumm, da ist meine Ausnahme - wieder Oracle. Aber es war nicht kommen, auch mit jeder einzelnen Zeile innerhalb einer try/catch, bis ich den Code aus dem Timer-Ereignis verschoben und versuchte es erneut].

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X