Entwickler-Ecke

Windows API - TService - Dienst stoppen/pause geht nicht


holgerbremen - Mi 20.12.06 14:47
Titel: TService - Dienst stoppen/pause geht nicht
Ich habe eine TService-Applikation. Soweit läuft auch alles, jedenfalls installieren und starten kann ich sie.

Nur Stoppen und Pause des Dienstes funktioniert nicht. Die Eigenschaften AllowPause/Stop sind auf true, daran kann es nicht liegen.

Hat jemand eine Ahnung, wo das Problem ist.

Gruß Holger


wdbee - Mi 20.12.06 15:46

Was treibt dein Dienst denn so? Wenn er eine Anforderung zum Stoppen oder zur Pause bekommt, muss er (auch in der Arbeitsschleife) darauf reagieren.

Wir haben auch Dienste, die wir intern verwenden, bei denen das nicht beachtet wird. Nach dem Start bzw. um bestimmte Zeiten legen die los und arbeiten einige Zeit. Während der Bearbeitung reagieren sie nicht auf Stop/Pause. Wenn die Arbeit abgeschlossen ist, dann klappt es.

Als Ergebnis bekommt man eine Fehlermeldung, wenn versucht wird, den Dienst zu beenden.
Da aber klar ist, wann er arbeitet, stört das niemand.


holgerbremen - Mi 20.12.06 16:45

Das Programm erzeugt Timer-Gesteuert alle 60 Sekunden PDF-Dateien. Die Daten werden aus einem MS-SQLServer gelesen. In der Procedure ServiceExecute() mache ich eigentlich gar nichts, außer zu sleepen. Ich versuche den Dienst schon zu stoppen, wenn gerade keine Verarbeitung läuft, aber auch dann gehts nicht.

Kann es an der ServiceExecute()-Funktion liegen.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TTQMSaverFabo.ServiceExecute(Sender: TService);
begin

  while not Terminated do
  begin
    Sleep(200); 
  end;

end;

Gruß

PS: Wie füge ich hier eigentlich Code ein?

Moderiert von user profile iconTino: Delphi-Tags hinzugefügt


Tino - Mi 20.12.06 16:56

user profile iconholgerbremen hat folgendes geschrieben:
PS: Wie füge ich hier eigentlich Code ein?


http://www.delphi-forum.de/help_schreiben_bbcodes_source.html


wdbee - Do 21.12.06 11:15

Was bei dir fehlt, ist die Bearbeitung von Nachrichten (ProcessRequests).
Das muss über den ServiceThread erfolgen, der dem Dienst zugeordnet ist.
Ein Timer ist nicht notwendig, aber möglich.
Für einfache Fälle:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
//==========================================================================*
// Working loop
//--------------------------------------------------------------------------*
procedure TXYZ_Service.ServiceExecute(Sender: TService);
begin
  // Hier wird Initialisiert (notfalls Timer starten)

  while not Terminated do                        // Solange Endeflag nicht gesetzt ist
  begin                                          // Anstehende Nachrichten bearbeiten
    ServiceThread.ProcessRequests(False);        // aber nicht auf Nachrichten warten

    // Hier arbeiten (falls nicht im Timer Event realisiert)
    ...

    Sleep(200);                                  // ggf. CPU-Last begrenzen
                                                 // bzw. Wiederholrate (statt Timer)
  end;

  // Hier wird aufgeräumt
end;
//==========================================================================*

Das sollte für viele Fälle schon ausreichen. Probleme gibt es, wenn die Arbeit (bzw. ein Durchlauf der while-Schleife) länger dauert, als die Wartezeit des Dienstecontrollers es akzeptiert.

Hier mal ein Gerüst, dass auch für kompliziertere Fälle geeignet ist. Die eigentliche Arbeit erfolgt im Datenmodul (Execute).

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
//==========================================================================*
// Moduldefinition
//--------------------------------------------------------------------------*
type
  TPCbCS_Service = class(TService)
    procedure ServiceExecute(Sender: TService);
  private
    procedure ProcessRequests(Sender: TObject);
    procedure Terminate;

  public
    function GetServiceController: TServiceController; override;
  end;
//==========================================================================*

//==========================================================================*
// Globale Variable
//--------------------------------------------------------------------------*
var
  PCbCS_Service: TPCbCS_Service;
//==========================================================================*

Wir wollen als erste erfahren, dass der Dienst beendet werden soll. Deshalb schleifen wir uns hier ein:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
//==========================================================================*
// Check for terminate command
//--------------------------------------------------------------------------*
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  if CtrlCode = 1 then                // Hier erfahren wir aus erster Hand
    PCBCS_Service.Terminate;          // das der Dienst beendet werden soll!
  PCBCS_Service.Controller(CtrlCode); // Normalen Ablauf durchführen 
end;
//==========================================================================*

Das was bei dir gefehlt hatte, wird in der eigentlichen Arbeitsschleife des Datenmoduls
zyklisch aufgerufen, so dass der Dienst schnell genug auf Anforderungen des Controllers reagieren kann.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
//==========================================================================*
// Process requests (Als Callback für das Datenmodul)
//--------------------------------------------------------------------------*
procedure TPCbCS_Service.ProcessRequests(Sender: TObject);
begin
  ServiceThread.ProcessRequests(False);          // Process Messages
end;
//==========================================================================*

An dieser Stelle wird die Arbeit einfach delegiert.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
//==========================================================================*
// Working loop
//--------------------------------------------------------------------------*
procedure TPCbCS_Service.ServiceExecute(Sender: TService);
begin
  DMGlobal.OnProcessRequests := ProcessRequests; // set event handler
  DMGlobal.Execute;                              // work
  // Rückkehr erst nach Terminate!
end;
//==========================================================================*

Die eigentliche Arbeit ist im Datenmodul bequemer unterzubringen, wenn dort Komponenten abgelegt werden müssen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
//==========================================================================*
// Ausgelagerte Arbeitsschleife
//--------------------------------------------------------------------------*
function TDMGlobal.Execute: Boolean;
begin
  // Initialisieren

  // Arbeitsschleife
  while not oTerminated do
  begin
    // in kleine Schritten Arbeiten
    ...
    if Assigned(OnProcessRequests) then          
      OnProcessRequests(self);
    if oTerminated then
      break;

    // in kleine Schritten Arbeiten
    ...
    // ggf. mehrfach einbauen, damit die Reaktionszeit kurz bleibt
    if Assigned(OnProcessRequests) then                
      OnProcessRequests(self);
    if oTerminated then
      break;
  end;

  // Aufräumen
  Result := True;
end;
//==========================================================================*

Anforderung des Controllers umsetzen lassen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
//==========================================================================*
// Stop service
//--------------------------------------------------------------------------*
procedure TPCbCS_Service.Terminate;
begin
  DMGlobal.Terminate;
end;
//==========================================================================*

Dabei ist wichtig, dass nicht alle diese Funktionen im gleichen Kontext ausgeführt werden! Der Aufruf von TDMGlobal.Terminate läuft im Kontext des Controllers, weshalb die Anweisung Sleep(xyz) nicht die Arbeitsschleife blockiert.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//==========================================================================*
// Beenden
//--------------------------------------------------------------------------*
procedure TDMGlobal.Terminate;
begin
  oTerminated := True; // Flag setzen
  Sleep(xyz);          // Der Arbeitsschleife Zeit für Reaktion geben
end;
//==========================================================================*


holgerbremen - Do 21.12.06 15:14

@wdbee

Danke, das war es doch schon. Die Funktion ProcessRequests() fehlte mir noch. Jetzt läßt sich der Service starten/stoppen und pausen.



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
//==========================================================================*  
// Working loop  
//--------------------------------------------------------------------------*  
procedure TXYZ_Service.ServiceExecute(Sender: TService);  
begin  
  // Hier wird Initialisiert (notfalls Timer starten)  
 
  while not Terminated do                        // Solange Endeflag nicht gesetzt ist  
  begin                                          // Anstehende Nachrichten bearbeiten  
    ServiceThread.ProcessRequests(False);        // aber nicht auf Nachrichten warten  
 
    // Hier arbeiten (falls nicht im Timer Event realisiert)  
    ...  

 
    Sleep(200);                                  // ggf. CPU-Last begrenzen  
                                                 // bzw. Wiederholrate (statt Timer)  
  end;  

 
  // Hier wird aufgeräumt  
end;  
//==========================================================================*


Besten Dank noch für das ganze Programmgerüst. Muss ich mir mal in Ruhe anschauen.

Gruß
Holger