webentwicklung-frage-antwort-db.com.de

Der Windows-Dienst OnStop wartet auf die abgeschlossene Verarbeitung

Ich entwickle tatsächlich einen Windows-Dienst in VS 2012/.NET 4.5.

Der Dienst folgt dem Schema des folgenden Code-Snippets: 

  • Mit einem Timer
  • Führt alle paar Minuten eine gewünschte Operation aus. 
  • Der Vorgang dauert etwa 10 Minuten
  • Ich verwende einen einzigen Thread im Dienst

Ich mache mir Sorgen, wenn jemand den Dienst über die Verwaltungskonsole anhält, kann es sein, dass der Dienst gerade während des Prozesses ausgeführt wird.

Ich habe etwas über das Stoppen des Windows-Dienstes mit Anforderungsstopp gelesen, bin aber etwas verloren gegangen. Manchmal werden WorkerThreads erstellt, manchmal ManualResetEvents, aber bis jetzt konnte ich den besten Weg für meinen Windows-Dienst nicht vollständig erfassen.

Ich muss warten, bis die Verarbeitung in der onStop-Methode ordnungsgemäß abgeschlossen ist, bevor der Windows-Dienst angehalten wird.

Was ist der beste Weg nach vorne, auch in Anbetracht des folgenden Codeausschnitts?

Danke euch allen!

namespace ImportationCV
{
    public partial class ImportationCV : ServiceBase
    {
        private System.Timers.Timer _oTimer;       

        public ImportationCV()
        {
            InitializeComponent();

            if (!EventLog.SourceExists(DAL.Utilities.Constants.LOG_JOURNAL))
            {
                EventLog.CreateEventSource(DAL.Utilities.Constants.LOG_JOURNAL,     DAL.Utilities.Constants.SOURCE_JOURNAL);
            }

            EventLog.Source = DAL.Utilities.Constants.SOURCE_JOURNAL;
            EventLog.Log = DAL.Utilities.Constants.LOG_JOURNAL;
        }

        protected override void OnStart(string[] args)
        {            
            int intDelai = Properties.Settings.Default.WatchDelay * 1000;

            _oTimer = new System.Timers.Timer(intDelai);
            _oTimer.Elapsed += new ElapsedEventHandler(this.Execute);

            _oTimer.Start();           

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " started at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        protected override void OnStop()
        {

            if (_oTimer != null && _oTimer.Enabled)
            {
                _oTimer.Stop();
                _oTimer.Dispose();
            }

            EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, "Service " + DAL.Utilities.Constants.SERVICE_TITLE + " stopped at " + DateTime.Now.ToString("HH:mm:ss"), EventLogEntryType.Information);
        }

        private void Execute(object source, ElapsedEventArgs e)
        {
            _oTimer.Stop();

            try
            {                
                //Process


            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(DAL.Utilities.Constants.LOG_JOURNAL, (ex.StackTrace + ("\r\n" + ex.Message)), EventLogEntryType.Error);
            }

            _oTimer.Start();
        }
    }
}
13
crisjax

Als Testfall rufe ich System.Threading.Thread.Sleep(500000) im OnStop() Rückruf meines Windows-Dienstes an. Ich habe den Dienst gestartet und dann beendet. In dem Fenster mit der Statusanzeige wurde angezeigt, dass der Service Control Manager (SCM) versucht hat, den Dienst zu beenden. Nach ungefähr 2 Minuten erhielt ich diese Antwort vom SCM:

enter image description here

Nachdem ich dieses Fenster geschlossen hatte, änderte sich der Status meines Dienstes im SCM in Stoppen und ich bemerkte, dass der Dienst im Task-Manager weiterhin ausgeführt wurde. Nachdem der Schlaf verstrichen war (fast 6 Minuten später), stoppte der Prozess. Beim Aktualisieren des SCM-Fensters wurde festgestellt, dass der Dienst nicht mehr ausgeführt wird.

Ich nehme ein paar Dinge davon weg. Erstens sollte OnStop() wirklich versuchen, den Dienst rechtzeitig zu beenden, nur als Teil des Spiels Nice mit dem System. Zweitens, je nachdem, wie Ihre OnStop() -Methode strukturiert ist, können Sie could den Dienst zwingen, eine vorbeugende Anforderung zum Beenden zu ignorieren, anstatt anzuhalten, wenn Sie dies sagen. Dies wird nicht empfohlen, aber es scheint, dass Sie könnte dies tun.

Was Ihre spezielle Situation betrifft, müssen Sie verstehen, dass das Ereignis System.Timers.Timer.Elapseddas auf einen ThreadPool -Thread ausgelöst wird . Per Definition ist dies ein Hintergrund-Thread , was bedeutet, dass die Anwendung nicht weiter ausgeführt wird. Wenn der Dienst zum Herunterfahren aufgefordert wird, stoppt das System alle Hintergrundthreads und beendet dann den Prozess. Ihre Sorge, die Verarbeitung so lange fortzusetzen, bis sie abgeschlossen ist, obwohl der SCM Sie auffordert, das System herunterzufahren , kann daher nicht so auftreten , wie Sie es derzeit strukturiert haben. Dazu müssen Sie ein formales System.Threading.Thread - Objekt erstellen, es als Vordergrund-Thread festlegen und dann den Timer verwenden, um die Ausführung dieses Threads auszulösen (im Gegensatz zur Ausführung in Elapsed Rückrufen).

Trotzdem denke ich immer noch, dass Sie mit dem System gut spielen möchten, was bedeutet, dass der Dienst rechtzeitig heruntergefahren wird, wenn Sie dazu aufgefordert werden. Was passiert, wenn Sie zum Beispiel den Computer neu starten müssen? Ich habe es nicht getestet, aber wenn Sie den Dienst so lange weiter ausführen lassen, bis die Verarbeitung abgeschlossen ist, wartet das System möglicherweise, bis der Vorgang abgeschlossen ist, bevor es neu gestartet wird. Das ist nicht das, was ich von meinem Dienst erwarten würde.

Ich würde also eines von zwei Dingen vorschlagen. Die erste Möglichkeit wäre, die Verarbeitung in einzelne Abschnitte aufzuteilen, die einzeln ausgeführt werden können. Überprüfen Sie nach Abschluss jedes Abschnitts, ob der Dienst beendet wird. Wenn ja, beenden Sie den Thread ordnungsgemäß. Wenn dies nicht möglich ist, würde ich etwas Ähnliches wie Transaktionen für Ihre Verarbeitung einführen. Angenommen, Sie müssen mit einer Reihe von Datenbanktabellen interagieren, und die Unterbrechung des Ablaufs nach dem Start wird problematisch, da die Datenbank möglicherweise in einem fehlerhaften Zustand verbleibt. Wenn das Datenbanksystem Transaktionen zulässt, wird dies relativ einfach. Wenn nicht, führen Sie die gesamte im Speicher verfügbare Verarbeitung durch und übernehmen Sie die Änderungen in der letzten Sekunde. Auf diese Weise blockieren Sie das Herunterfahren nur, während die Änderungen festgeschrieben werden, und nicht das Blockieren für die gesamte Dauer. Und für das, was es wert ist, bevorzuge ich die Verwendung von ManualResetEvent für die Übermittlung von Befehlen zum Herunterfahren an Threads.

Um nicht weiter zu streifen, werde ich es hier abschneiden. HTH.

BEARBEITEN:

Dies ist nicht in der Manschette, daher werde ich die Genauigkeit nicht überprüfen. Ich werde alle Probleme beheben, die Sie (oder andere) finden.

Definieren Sie zwei ManualResetEvent Objekte, eines für die Benachrichtigung über das Herunterfahren und eines für die Verarbeitung der Benachrichtigung, und das Thread Objekt. Ändern Sie den OnStart() -Rückruf folgendermaßen:

using System.Threading;
using Timer = System.Timers.Timer; // both Threading and Timers have a timer class

ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
ManualResetEvent _processEvent  = new ManualResetEvent(false);
Thread _thread;
Timer _oTimer;

protected override void OnStart(string[] args)
{
    // Create the formal, foreground thread.
    _thread = new Thread(Execute);
    _thread.IsBackground = false;  // set to foreground thread
    _thread.Start();

    // Start the timer.  Notice the lambda expression for setting the
    // process event when the timer elapses.
    int intDelai = Properties.Settings.Default.WatchDelay * 1000;
    _oTimer = new Timer(intDelai);
    _oTimer.AutoReset = false;
    _oTimer.Elapsed += (sender, e) => _processEvent.Set();
    _oTimer.Start();
}

Ändern Sie Ihren Execute() -Rückruf folgendermaßen:

private void Execute()
{
    var handles = new WaitHandle[] { _shutdownEvent, _processEvent };

    while (true)
    {
        switch (WaitHandle.WaitAny(handles))
        {
            case 0:  // Shutdown Event
                return; // end the thread
            case 1:  // Process Event
                Process();
                _processEvent.Reset();  // reset for next time
                _oTimer.Start();        // trigger timer again
                break;
        }
    }
}

Erstellen Sie die Process() -Methode wie folgt:

private void Process()
{
    try
    {
        // Do your processing here.  If this takes a long time, you might
        // want to periodically check the shutdown event to see if you need
        // exit early.
    }
    catch (Exception ex)
    {
        // Do your logging here...

        // You *could * also shutdown the thread here, but this will not
        // stop the service.
        _shutdownEvent.Set();
    }
}

Triggern Sie schließlich im OnStop() -Rückruf den Thread zum Herunterfahren:

protected override void OnStop()
{
    _oTimer.Stop();  // no harm in calling it
    _oTimer.Dispose();

    _shutdownEvent.Set();  // trigger the thread to stop
    _thread.Join();        // wait for thread to stop
}
19
Matt Davis

@ Matt - Danke für den tollen Code, wirklich hilfreich. Ich fand es noch besser, wenn ich einen weiteren Test zu _shutdownEvent hinzufügte:

case 1:  // Process Event
            Process();
            if(_shutdownEvent.WaitOne(0)) break; // don't loop again if a shutdown is needed
...
0
Allister