Page 1 of 2 1 2 LastLast
Results 1 to 15 of 18

Thread: Thread in een service

  1. #1

    Thread in een service

    Helaas is mijn kennis van threads wat roestig.
    Heb ondertussen al wel wat artikelen doorgelezen een aan de hand daarvan iets gemaakt maar wilde even controleren of ik het allemaal wel goed doe

    De situatie is als volgt ik wil 1x per uur (of een ander lang interval) via een RestClient de data uitlezen van een device.
    Deze data wordt vervolgens naar een database geschreven.
    Dit moet op de achtergrond draaien vandaar dat ik heb gekozen voor een Service

    Nu schijnt het zo te zijn dat je in Services geen VCL kan/moet gebruiken
    Dus ik heb een service gemaakt die een Object van TThread aanspreekt
    In de execute van de thread haal ik via de PerformanceCounter de tijd op en wanneer het interval verstreken is voer ik de taak uit.
    Daarbij wil ik wel een seintje geven aan het hoofdprogramma dat de taak is uitgevoerd (zodat deze dit in de logfile kan schrijven)

    Tevens heb ik een kleine applicatie gemaakt om het e.e.a. te test
    Deze app doet hetzelfde als de Service alleen laat deze steeds op het scherm zien wat er uitgelezen wordt

    Onderstaande manier werkt wel maar zorgt ervoor dat mijn hoofd programma niet goed reageert.

    Code:
    QueryPerformanceFrequency(frequency64);
    QueryPerformanceCounter(startTime64);
    
    while not Terminated do
    begin
      QueryPerformanceCounter(endTime64);
      elapsedSeconds := (endTime64 - startTime64) / frequency64 ;
      if ElapsedSeconds>=FInterval then
      begin
       ReadData;
       Write2DB;
       if Assigned(FOnUpdate) then FOnUpdate(self);
       QueryPerformanceCounter(startTime64);
      end;
    // hier moet ik dus iets syncen
    end;
    Wat doe ik nog fout

  2. #2
    Waarom een thread in een service?
    Je kunt toch gewoon een enkele TService maken die de uitlees doet.
    Dat hoeft dan niet in een thread want het heeft toch geen GUI.
    Dan kun je ook gewoon direct naar de log schrijven.
    https://docwiki.embarcadero.com/RADS...e_Applications

    Uiteraard moet je je testprogramma niet tegelijk laten lopen met die service want ik neem aan dat je niet tegelijk die data uit kunt lezen.

  3. #3
    Ik ben uitgekomen op de thread omdat ik maar eens in het uur de data wil ophalen/weg wil schrijven.
    Daarnaast had ik gelezen dat je binnen een service geen timers moet gebruiken.
    Kortom ik moet dus iets maken wet ieder uur een signaaltje afgeeft maar waardoor het programma zo min mogelijk resources gebruikt

  4. #4
    Kijk even in die link die ik meegaf.
    Het grappige is hier dat je in de thread niets anders hoeft te doen.

    Je hebt dus in je TService1.Service1Execute een loop met not terminated en je kunt daar gewoon in controleren of je een uur verder bent en dan de data ophalen.

    Omdat de service verder niets hoeft te doen is het helemaal niet nodig om een thread daarin te gebruiken. Code kan gewoon een paar regels lang zijn.

    Als je met een normaal GUI programma werkt dan moet je wel met een thread werken want anders wordt je GUI unresponsive. Maar in een service (die alleen de data op hoeft te halen) is dat niet nodig.

    Wat je nu in feite doet met een thread in een service is hetzelfde als dat jij in een GUI programma een thread start en vanuit die thread WEER een thread start. Totaal overbodig dus

  5. #5
    Top bedankt voor de uitleg!!

  6. #6
    Niet helemaal waar, volgens mij.
    Services moeten wel messages van het OS kunnen ontvangen en beantwoorden, om aan te geven dat ze niet hangen.

    Het hoeft allemaal een stuk minder responsive te zijn dan een UI, maar als het uitlezen en verwerken van die data lang duurt, dan kan de service als non-responsive worden gezien.

    Bij normale applicaties gebeurt dat (het weergeven van "reageert niet" in de titel, bijvoorbeeld), als een applicatie 5 seconden lang niet PeekMessage heeft aangeroepen.
    Bij services is dat mogelijk hetzelfde, al zal dat niet per se een probleem zijn.

    Maar als je probeert een service te stoppen, wordt er een speciaal bericht heen gestuurd, en de verwachting is dat daar binnen 20 seconden op gereageerd wordt. Zo niet, dan kan Windows je service killen.

    Al met al lijkt het me best een goed idee om ook binnen een service langdurige operaties in threads te stoppen, en ondertussen te blijven reageren op input van buitenaf.
    1+1=b

  7. #7
    Quote Originally Posted by GolezTrol View Post
    Niet helemaal waar, volgens mij.
    Services moeten wel messages van het OS kunnen ontvangen en beantwoorden, om aan te geven dat ze niet hangen.

    Het hoeft allemaal een stuk minder responsive te zijn dan een UI, maar als het uitlezen en verwerken van die data lang duurt, dan kan de service als non-responsive worden gezien.

    Bij normale applicaties gebeurt dat (het weergeven van "reageert niet" in de titel, bijvoorbeeld), als een applicatie 5 seconden lang niet PeekMessage heeft aangeroepen.
    Bij services is dat mogelijk hetzelfde, al zal dat niet per se een probleem zijn.

    Maar als je probeert een service te stoppen, wordt er een speciaal bericht heen gestuurd, en de verwachting is dat daar binnen 20 seconden op gereageerd wordt. Zo niet, dan kan Windows je service killen.

    Al met al lijkt het me best een goed idee om ook binnen een service langdurige operaties in threads te stoppen, en ondertussen te blijven reageren op input van buitenaf.
    Ja , dat wordt toch allemaal in TService geregeld?
    Dan kun je toch in TService1.Service1Execute een tight loop maken met een sleep(100) en controleren op Terminated (zoals in het voorbeeld op de site van Embarcadero).
    In de loop kun je dan met GetTickCounter64 (of iets anders) controleren of er een uur is verlopen.
    Geen gedoe met PeekMessage nodig.

    Als je echt een bare-boned service wilt maken zonder TService, ja dan moet je dat natuurlijk wel doen.

    Of mis ik nu het punt van de TService en wordt dat daar niet in afgehandeld?

    Edit: Ja, zolang je in je loop ook een call doet naar ServiceThread.ProcessRequests(true); dan gaat dat goed.
    SeriveThread is een property van TService om juist die dingen af te handelen.
    Je hoeft dus niet zelf een thread te maken.

    Mag natuurlijk wel als je dat liever hebt
    Last edited by rvk; 02-Mar-23 at 13:28.

  8. #8

  9. #9
    Ik loop nu id er tegen aan dat wanneer ik mijn service laat lopen ik een flinke load trekt op mijn processor (25%) Heb waarschijnlijk een QuadCore ;-)
    Daarnaast krijg ik allerlei meldingen dat het systeem niet meer reageert
    Met een taskkill uiteindelijk het proces kunnen stoppen.

    Kortom is er een betere manier om zo'n service te laten draaien

    Wellicht dat de sleep(100) wonderen doet hoor

  10. #10
    De sleep(100) doet wonderen!

    Echter de service wordt niet netjes gestart want na 20 seconden krijg ik de volgende melding
    Windows could not start the service on the local computer
    Error 1053 the service did not respond to the strat or control request in a timely fashion

    Kortom deze methode houdt de boel op slot

    Hoe kan je dit nu op een juiste manier doen!

  11. #11
    Die mobiele versie hier van het forum is nog steeds klote. Je kunt geen bericht wijzigen want dan wordt het gelijk verwijderd
    ~~

    Zonder code die je hebt is het moeilijk te zeggen wat er mis gaat.

    Je roept wel ServiceThread.ProcessRequests(false) aan in je loop?

    (De false gebruik je omdat je wel wil dat je eigen code doorloopt en dat combineer je dan met een sleep(100))

  12. #12
    Je thread zit in een constant loop QueryPerformanceCounter aan te roepen, sleep(100) en zelfs sleep(0) zal wel wat helpen maar het blijft een beetje onzin om maar te blijven loopen voor iets wat maar 1x per uur hoeft te gebeuren. Nu is die code zeker te verbeteren maar zou het niet gewoon eenvoudiger zijn om een scheduled task te gebruiken die eens per uur wordt aangeroepen?

  13. #13
    Ok voordat we langs elkaar heen gaan praten weer even terug naar de code die ik nu heb staan en wat het orginele idee is.
    Ik wil dus een service maken die 1x per uur data ophaalt van een aparaat en dit weg schrijft in een database.
    Ik had daarbij bedacht om dit in een thread te doen maar dat is blijkbaar niet nodig.
    Inmiddels heb ik de thread verwijderd.
    Bij het Onstart Event van de Service start ik mijn (infinite) loop.
    Hierdoor komt de service nooit uit zijn startroutine en geeft na een bepaalde tijd een time out error

    hierbij wat code

    Code:
    procedure ServiceController(CtrlCode: DWord); stdcall;
    begin
      HWP1Reader.Controller(CtrlCode);
    end;
    
    function THWP1Reader.GetServiceController: TServiceController;
    begin
      Result := ServiceController;
    end;
    
    procedure THWP1Reader.ServiceStart(Sender: TService; var Started: Boolean);
    begin
    ServiceThread.ProcessRequests(false);
    LogMessage('HWP1 started',EVENTLOG_AUDIT_SUCCESS,1,121);
    HWP1:=THWP1.Create(10);
    HWP1.Start;
    
    end;
    
    procedure THWP1Reader.ServiceStop(Sender: TService; var Stopped: Boolean);
    begin
    LogMessage('HWP1 stopped',EVENTLOG_AUDIT_SUCCESS,1,122);
    HWP1.Free;
    end;
    en het object dat de calls e.d. doet

    Code:
    procedure THWP1.Start;
    var
      startTime64, endTime64, frequency64: Int64;
      elapsedSeconds: single;
    
    begin
    inherited;
    
    QueryPerformanceFrequency(frequency64);
    QueryPerformanceCounter(startTime64);
    
    while not Stopped do
    begin
      QueryPerformanceCounter(endTime64);
      elapsedSeconds := (endTime64 - startTime64) / frequency64 ;
      if ElapsedSeconds>=FInterval then
      begin
       ReadData;
       Save2DB;
       QueryPerformanceCounter(startTime64);
      end;
      sleep(100);
    end;
    end;

  14. #14
    Wat ik hier mis is de loop de service zelf. In die loop moet ServiceThread.ProcessRequests aangeroepen worden.
    Jij doet dit nu alleen maar 1 keer bij de start. Dat gaat dus niet werken.
    En wat is THWP1Reader?

    De meest simpele service is dit:

    Delphi Code:
    1. unit Unit1;
    2.  
    3. interface
    4.  
    5. uses
    6.   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs;
    7.  
    8. type
    9.   TService1 = class(TService)
    10.     procedure ServiceStart(Sender: TService; var Started: Boolean);
    11.     procedure ServiceStop(Sender: TService; var Stopped: Boolean);
    12.     procedure ServiceExecute(Sender: TService);
    13.   private
    14.     { Private declarations }
    15.   public
    16.     function GetServiceController: TServiceController; override;
    17.     { Public declarations }
    18.   end;
    19.  
    20. var
    21.   Service1: TService1;
    22.  
    23. implementation
    24.  
    25. {$R *.dfm}
    26.  
    27. procedure ServiceController(CtrlCode: DWord); stdcall;
    28. begin
    29.   Service1.Controller(CtrlCode);
    30. end;
    31.  
    32. function TService1.GetServiceController: TServiceController;
    33. begin
    34.   Result := ServiceController;
    35. end;
    36.  
    37. procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
    38. begin
    39.   LogMessage('HWP1 started', EVENTLOG_AUDIT_SUCCESS, 1, 121);
    40. end;
    41.  
    42. procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
    43. begin
    44.   LogMessage('HWP1 stopped', EVENTLOG_AUDIT_SUCCESS, 1, 122);
    45. end;
    46.  
    47. procedure TService1.ServiceExecute(Sender: TService);
    48. var
    49.   startTime64, endTime64, frequency64: Int64;
    50.   elapsedSeconds: single;
    51. begin
    52.   QueryPerformanceFrequency(frequency64);
    53.   QueryPerformanceCounter(startTime64);
    54.   HWP1.Create( ?? );
    55.   while not Terminated do
    56.   begin
    57.     QueryPerformanceCounter(endTime64);
    58.     elapsedSeconds := (endTime64 - startTime64) / frequency64;
    59.     if elapsedSeconds >= 60 then
    60.     begin
    61.       HWP1.ReadData; // MOET direct terugkeren
    62.       HWP1.Save2DB;
    63.       QueryPerformanceCounter(startTime64);
    64.     end;
    65.     ServiceThread.ProcessRequests(false);
    66.     Sleep(100);
    67.   end;
    68.   HWP1.Free;
    69. end;
    70.  
    71. end.

    De TService1.ServiceExecute MOET dus een loop bevatten waar je ServiceThread.ProcessRequests aanroept.

    Als je echt een aparte class wilt maken voor THWP1, dan moet die of
    1) de THWP1.Read direct terugkeren naar de loop van de service
    2) in de loop van THWP1 een callback doen naar ServiceThread.ProcessRequests
    3) of toch weer alles als service implementeren, MAAR dan MOET je wel de ServiceExecute houden met een strakke loop
    Delphi Code:
    1. while not Terminated do
    2.   begin
    3.     ServiceThread.ProcessRequests(TRUE); //  hier kun je dan TRUE gebruiken
    4.   end;
    Als je ServiceThread.ProcessRequests(TRUE); gebruikt dan hoef je geen sleep te doen want TRUE wacht op een antwoord (en blokt dus de verdere loop). Dat gebruik je dus alleen als je een thread in de service gebruikt. Anders gebruik je zoals ik hierboven liet zien False met je eigen loop in ServiceExecute.

  15. #15
    Thanks,

    zo werkt het idd wel goed.
    De HWP1 is een instance van de class THWP1.
    Dit wil ik wel graag een apart object houden zodat ik ook makkelijk een test programmaatje kan maken en eventueel de functionaliteit (teruglezen uit de database en export e.d.) kan toevoegen

    No wel een vraag over de opmerking dat de call direct moet terugkeren
    In de readdata doe ik via een restclient een call naar mijn device.
    Mocht het device nu uitstaan o.i.d. dan duurt het even voordat de restclient een timeout geeft
    Dit i.c.m. de eerdere opmerking zou dus een conflict kunnen geven

    Code:
    var
      Literator      : TJSONIterator;
      LJsonTextReader: TJsonTextReader;
      LStringReader  : TStringReader;
      st        : string;
    begin
    
    try
      st              := RestBuilder.CreateRequest.Resource(Adress+APICall).Get.ResponseContentAsJson(FALSE);
    //  st              := RestBuilder.CreateRequest.Resource(ARGUS_WEB_API).BasicAuth(USERNAME, PASSWORD).Get.ResponseContentAsJson(FALSE);
      LStringReader   := TStringReader.Create(st);
      LJsonTextReader := TJsonTextReader.Create(LStringReader);
      Literator       := TJSONIterator.Create(LJsonTextReader);
    
      Literator.Recurse; // prepare to enter array;
    
      while Literator.Next do
      begin
       if Literator.Key ='total_power_import_kwh' then Data.TotalPImpAll:=Literator.AsVariant;
       if Literator.Key ='total_power_import_t1_kwh' then Data.TotalPImpT1:=Literator.AsVariant;
       if Literator.Key ='total_power_import_t2_kwh' then Data.TotalPImpT2:=Literator.AsVariant;
       if Literator.Key ='total_power_export_kwh' then Data.TotalPExpAll:=Literator.AsVariant;
       if Literator.Key ='total_power_export_t1_kwh' then Data.TotalPExpT1:=Literator.AsVariant;
       if Literator.Key ='total_power_export_t2_kwh' then Data.TotalPExpT2:=Literator.AsVariant;
       if Literator.Key ='total_gas_m3' then Data.TotalGas:=Literator.AsVariant;
      end;
    finally
      begin
    
    
      end;
    end;
    
    LStringReader.Free;
    LJsonTextReader.Free;
    Literator.Free;

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •