Net-Base Žurnāls

02.08.2026

Windows pakalpojums Delphi: kontrolēta apturēšana ar TEvent un Stop-Timeout — pareiza īstenošana

Wenn ein Windows-Service beim Stoppen „hängt“, ist das selten Zufall: Meist blockieren Threads, I/O oder Sleep-Schleifen ohne Abbruchpfad. Dieser Praxisbeitrag zeigt, wie du in Delphi mit TEvent einen sauberen Graceful Shutdown implementierst, Stop-Timeouts korrekt behandelst...

02.08.2026

No žurnāla tēmas līdz projektu praksei

Atbilstošas pakalpojumu un tehniskās lapas rakstam

Viens Windows Service in Delphi ikdienā bieži izskatās nepamanāms: darbojas fonā, apstrādā uzdevumus, raksta logus, sazinās ar datubāzēm vai REST-API. Līdz kāds noklikšķina „Apturēt pakalpojumu“ — vai nepieciešams patch-reboot — un serviss nekorekti neapstājas. Tad Pakalpojumu konsole minūtēm rāda „Tiek apturēts…“, serviss paliek Stop Pending-statusā, un sliktākajā gadījumā process tiek piespiedu kārtā apturēts. Tieši šeit ir vērts uztvert Windows Service in Delphi kontrolētu izslēgšanu kā apzinātu arhitektūras tēmu: ar skaidru izslēgšanas signālu, definētiem laika limitiem un pavedieniem, kas patiešām reaģē.

Šajā rakstā netiks apspriesti framework iekšējie mehānismi, bet gan praksē lietojams paraugs: TEvent kā stop-signāls (kernel-tuvs sinhronizācijas objekts no System.SyncObjs), kombinēts ar Stop-Timeout-stratēģiju, kas ņem vērā gan Windows Service Control Manager (SCM, t.i., Windows-komponente, kas servisus palaiž/aptur), gan tavus pašu worker-pavedienus. Papildus tipiskie malas gadījumi, debugēšanas pieejas un jautājums, kad papildu loģika patiešām atmaksājas.

Windows Service in Delphi — kontrolēta izslēgšana praksē

Visbiežākais iemesls ir vienkāršs: servisam ir vismaz viens pavediens, kas iestrēgst bloķējošā operācijā un tam nav pārtraukšanas ceļa. Tipiskie gadījumi:

  • Polling-Schleifen mit Sleep: „while not Terminated do Sleep(1000)“. Apturot servisu signāls nonāk, bet pavediens reaģē tikai pēc līdz pat 1 sekundes (vai 30 sekundēm…).
  • Blockierende I/O: datubāzu izsaukumi, HTTP-requests, Named Pipes, failu sistēmas gaidīšanas — viss, kas „vienkārši gaida“, neņemot vērā apturēšanas signālu.
  • Queue-Consumer ohne Wakeup: workers gaida rindā, bet apturot tos nepamodina, lai tie izietu.
  • Lock-Reihenfolge/Deadlocks: apturošanas laikā tiek veikts „Cleanup“, kamēr citi pavedieni vēl tur bloķējumus. Tas bieži notiek tikai apturēšanas ceļā, jo tur secība atšķiras no normālā darbības laika.

Windows SCM sagaida, ka serviss uz Stop-komandu reaģēs zibenīgi un nepārtraukti ziņos par savu statusu (izmantojot SetServiceStatus; Delphi to iepakot Service-komponentē). Ja tu pieņem Stop-notikumu, bet savus pavedienus neizslēdz tīri, process paliek dzīvs — un Windows kādā brīdī nolems, ka tas „ņem pārāk ilgi“. Rezultāts ir vai nu piespiedu pārtraukšana, vai serviss, kas paliek iestrēdzis neskaidrā starpfāzē.

Pamatprincipi: apturēšanas signāls, ko saprot katrs darba pavediens

Abstrakta grafika: darba pavedieni gaida uz darbu vai apturēšanas notikumu un secīgi beidz darbību
Kad darba pavedieni gaida „darbu vai apturēšanu“, apturēšanas latentums samazinās bez pollēšanas.

Kontrolēta izslēgšana darbojas tikai tad, ja tev ir signāls, kas:

  • var tikt novērots no visiem attiecīgajiem darba pavedieniem,
  • spēj iedarboties arī no bloķējošiem gaidīšanas stāvokļiem,
  • pārtraukšanas ceļā deterministisks ir (bez “varbūt viņš kādreiz iznāks” cerības),
  • ir skaidra Timeout-stratēģija.

In Delphi ist TEvent dafür ein sehr brauchbares Werkzeug: ein Event-Objekt, das intern über Windows-Handles umgesetzt ist (vergleichbar mit CreateEvent/SetEvent). Du kannst es als „Stop requested“-Signal verwenden. Jeder Worker wartet dann nicht einfach blind, sondern wartet „auf Arbeit oder auf Stop“.

TEvent richtig wählen: ManualReset vs. AutoReset

Parasti pārtraukšanas signāliem izvēlas Manual Reset (manuāli atiestatāms): vienreiz iestatīts, notikums paliek „signaled“, līdz to atiestatāt. Tas nodrošina, ka jebkura darba vītne, kas vēlāk nonāk gaidīšanas fāzē, tomēr atpazīst Stop signālu. Auto Reset šeit būtu riskants, jo tas pēc vienas gaidošas vītnes automātiski atiestata signālu un citas vītnes Stop signālu var palaist garām.

Delphi-Service-Lebenszyklus: Wo Stop wirklich ankommt

Ein Delphi-Windows- und Linux-Services basiert typischerweise auf TService (VCL/RTL). Der SCM schickt Kommandos (Start, Stop, Pause, Continue). Delphi ruft dann entsprechende Events/Methoden auf (je nach Template z. B. OnStart, OnStop, OnExecute).

Svarīgi arhitektūras ziņā:

  • OnStop nav vieta ilgstošai gaidīšanai bez statusa atjauninājumiem. Šajā vietā tu ierosini shutdown un pēc tam kontrolēti gaidi — ar laika ierobežojumu.
  • OnExecute bieži ir cilpa. Ja tur veic „bezgalīgu“ darbu, cilpai jāreaģē uz Stop signālu.
  • Worker-Threads (TThread oder Thread-Pools) jāreaģē uz to pašu Stop signālu, citādi serviss loģiski ir apturēts, bet fiziski vēl nav pabeigts.

Sauberes Muster: Stop-Event + Join der Worker + harter Fallback

Praktiskais modelis sastāv no četriem soļiem:

  1. Stop anfordern: iestatīt Stop-Event, vairs nepieņemt jaunus darbus.
  2. Wakeups auslösen: ja workeri gaida rindās vai ir miega režīmā, tiem jāspēj „pamosties“ (piem., ar Event/Queue signālu).
  3. Geordnet beenden: workeri beidz savas cilpas, aizver resursus (DB-savienojumi, faili, Handles) un ziņo „pabeigts“.
  4. Timeout und Fallback: ja viss neslēdzas laicīgi, jāpieņem lēmums — turpināt gaidīt (ar statusa atjauninājumu) vai kontrolēti pārtraukt/harti beigt (atkarībā no riska).

Būtiskākais ir: nevienai vītnei nedrīkst būt tikai laika gaidīšana (Sleep) vai tikai I/O bloķēšana, neņemot paralēli vērā Stop signālu. Tā vietā jāstrādā ar gaidīšanas funkcijām, kas ņem vērā vairākus signālus (piem., „Stop-Event oder Work-Event“), vai jāiekapsulē I/O ar timeoutiem plus Stop-pārbaudēm.

Stop-Timeout richtig denken: SCM-Timeout vs. eigener Shutdown-Timeout

Šeit projektos rodas lielākā daļa pārpratumu. Ir divi atšķirīgi Timeout-līmeņi:

  • SCM-Erwartung: Windows erwartet, dass du im Status SERVICE_STOP_PENDING regelmäßig Fortschritt meldest. Sonst wirkt es so, als hängst du. Delphi kümmert sich teilweise darum, aber sobald du selbst länger blockierst, brauchst du eine Strategie, wie du weiter Status-Updates ermöglichst (oder wie du deine Stop-Phase kurz hältst).
  • Tavs izslēgšanās laika limits: Tu definē, piemēram: „Mēs piešķiram 20 sekundes, lai tīri pabeigtu notiekošos darbus, pēc tam pārtraucam.” Tas ir arhitektūras lēmums: datu konsekvence pret piespiedu restartu pret ekspluatācijas prasībām.

Praktiski tas nozīmē: tavs serviss ātri jānonāk stāvoklī, kurā tas vairs nesāk jaunas darba vienības, un pēc tam tikai gaida, kad esošais darbs beigsies — bet ne bezgalīgi. Un šai gaidīšanas fāzei jādarbojas mazos intervālos, lai tu varētu reaģēt un, ja nepieciešams, ierakstīt logus.

Cik ilgi drīkst ilgt apturēšana?

Nav maģiskas vērtības, kas der visos gadījumos. Daudziem biznesa servisam mērķa diapazons 5–30 sekundes ir reāls: pietiekami laika notiekošiem datiem, bet pietiekami īsi patch logu vajadzībām. Ja tev regulāri nepieciešams ilgāk, tas parasti norāda, ka apstrādā pārāk lielas vienības vienā piegājienā vai ka ārējās atkarības (DB/HTTP) darbojas bez timeout.

Implementācija ar TEvent: arhitektūra, kas paliek stabila ekspluatācijā

Pārbaudīta arhitektūra Delphi servisa izskatās šādi (neiedziļinoties framework detaļās):

  • Viena Stop-Event (TEvent, Manual Reset), kas tiek iestatīta apturēšanas brīdī.
  • Viens vai vairāki Worker-Threads, kas savā galvenajā cilpā regulāri pārbauda Stop signālu.
  • Neobligāti Work-Event vai rinda (queue), kas signalizē darbu. Worker tad gaida „Work vai Stop”.
  • Viena Shutdown-Phase, kas veic worker ‚join‘ (t.i., gaida, līdz tie pabeidz darbu), bet ar timeout.

Galvenais nav tas, vai tu izmanto TThread, omnithreadlibrary vai savu poolu, bet gan tas, ka tavi workeri nedarbojas „akli”. Darbinieka cilpa strukturāli jāizskatās šādi: gaidīt notikumu(s) → darbs mazos gabalos → starp gabaliem pārbaudīt Stop → resursus tīri atbrīvot.

Riska punkts: Terminate vien nepietiek

Daudzi Delphi-threadi tiek ar Terminate „pārtraukti”. Taču tas ir tikai flags. Ja threads šobrīd atrodas bloķējošā API izsaukumā, vispirms nekas nenotiks. Tāpēc pašmērķa Stop-Event ir tik noderīgs: to var integrēt gaidīšanas izsaukumos un mērķtiecīgi izraisīt pamodināšanu.

Riska punkts: FreeOnTerminate servisa kontekstā

Servisos bieži redz FreeOnTerminate := True. Tas var darboties, bet padara izslēgšanos grūtāk kontrolējamu, jo bieži vairs nav tīras references, uz kuras gaidīt thread beigas un protokolēt kļūmes stāvokļus. Kontrolētai stop-loģikai parasti stabilāk ir skaidri piederēt threadiem un izslēgšanās laikā deterministiski gaidīt un atbrīvot tos.

Bloķējošas operācijas: kā padarīt tās pārtraucamas

Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
Bloķējošs I/O bez laika pārsnieguma ir visizplatītākais iemesls servisa apturēšanās iestrēgumiem.

Sarežģītākais nav pats notikums, bet vietas, kur jūsu serviss tiek bloķēts. Trīs tipiskas klases:

1) Sleep/Polling aizvietošana: gaidīšana ar Stop-Event

Ja jūsu process darbojas periodiski („ik pēc 10 sekundēm pārbaudīt“), nelietojiet Sleep(10000), bet gaidiet notikumu ar laika pārsniegumu (timeout). Tad jūsu Stop-Event var nekavējoties pārtraukt gaidīšanu. Tas samazina apturēšanas latentumu un novērš sajūtu „serviss nereagē“.

2) Queue-Consumer: Work-Event + Stop-Event kombinēšana

Ja jums ir Producer/Consumer arhitektūra (piem., darbi tiek ielikti rindā), jums vajag signālu, kas modina patērētājus. Bieži tas ir vēl viens TEvent (Work available). Patērētājs tad gaida uz diviem handles: „Work“ vai „Stop“. Pie Stop iestatāt Stop-Event un, ja nepieciešams, arī Work-Event, lai visi patērētāji garantēti izkļūtu no gaidīšanas.

3) Externe Calls (DB/HTTP): Timeouts und Abbruchpfade

Pie datu bāzes piekļuves vai HTTP izsaukumiem tiek izlemts, vai jūsu serviss tīri apstāsies. Darbībā: nav izsaukuma bez laika pārsnieguma. Laika pārsniegums nav greznība, bet priekšnoteikums pārvaldāmībai. Papildus jums jāpiemēro Stop pārbaude starp retries/backoff fāzēm. Citādi iegūsiet klasiku: „Serviss neapstājas, jo tas veic 10 retries ar Sleep“.

Dažās bibliotēkās var tieši izraisīt atcelšanu (piem., vaicājuma atcelšana). Ja tas nav iespējams, vismaz konfigurējiet laika pārsniegumus pietiekami īsus, lai nepārsniegtu izslēgšanas (shutdown) laika limitu.

Stop Pending pareizi: Statuss, Logging un cerību pārvaldība

Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
Fāžu žurnāli un laika mērījumi padara Stop-timeoutus reproducējamus un izskaidrojamus.

Kad serviss apstājas, no darbības viedokļa ir svarīgi saprast, kur tas iestrēgst. Tam nepieciešamas divas lietas:

  • Žurnālu marķieri apturēšanas ceļā: „Stop pieprasīts“, „nav jaunu darbu“, „gaidu Worker“, „Worker X pabeigts“, „Shutdown pabeigts“.
  • Mērāmas laika vērtības: Cik ilgi ilgst apturēšana? Kura fāze aizņem laiku? Šeit bieži pietiek ar monotonu laika mēru, piemēram, GetTickCount64 vai TStopwatch (monotons = netiek kropļots ar sistēmas laika izmaiņām).

Wenn du im apturēšanas ceļā nur einen einzigen Logeintrag „Stopping…“ schreibst, bleibt das Debugging im Feld ein Ratespiel. Im Service-Betrieb sind Logs häufig das einzige, was du ohne Interaktion bekommst.

Kuri žurnāli servisos patiešām ir noderīgi?

  • Service-PID, starta laiks, versija/Build (bez liekas noslodzes).
  • Aktīvo workeru skaits, in-flight jobu skaits.
  • Aktīvās ārējās atkarības: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (tikai apkopoti, ne katra detaļa).
  • Sasniegts Stop-Timeout: kuri workeri vēl ir atvērti?

Atkļūdošana laukā: reproduzierbar machen statt raten

Die Stop-Probleme treten gern nur in Produktion auf: andere Last, andere Latenzen, andere Rechte, andere Patch-Zeitfenster. Ein paar praxiserprobte Hebel:

Testēt servisu kontrolētā vidē

  • Apturēšana aktīvas apstrādes laikā (ne tad, kad ir Idle).
  • Apturēšana ārējas kļūmes laikā: DB īslaicīgi nepieejama, HTTP‑galapunkts lēns, Fileshare pazudis.
  • Apturēšana uzreiz pēc starta (Race-Conditions: workeri vēl tiek uzbūvēti).

Event Viewer und Service Control Manager Signale

Windows schreibt Dienstereignisse, aber die sind oft grob. Besser ist, wenn dein Service selbst in eine Logdatei oder Windows Event Log schreibt. Wichtig ist dabei: Logging sollte im Stop-Pfad noch funktionieren. Wenn du Logger im Shutdown zu früh freigibst oder Flush blockiert, verlierst du genau die entscheidenden Spuren.

Padarīt redzamus iestrēgušos pavedienus

Wenn du wiederholt „Stop Timeout“ siehst, lohnt sich ein Blick auf Thread-Zustände (z. B. per Debugger/Procdump in Testumgebung). Häufig findest du dann einen Thread in einem Wait-Zustand auf einem Handle, das nie signalisiert wird, oder in einem Netzwerk-Call ohne Timeout. Der Fix ist dann selten „noch mehr Sleep“, sondern ein sauberer Abbruchpfad.

Kad tas darbs patiešām atmaksājas?

Ein minimalistischer Dienst, der nur einen Timer hat und keine externen Abhängigkeiten, kann manchmal „einfach stoppen“. Sobald aber eines davon zutrifft, lohnt sich ein sauberer Graceful Shutdown fast immer:

  • Der Service verarbeitet Jobs mit Seiteneffekten (Dateien schreiben, DB-Transaktionen, API-Aufrufe).
  • Es gibt mehrere Threads oder einen Pool.
  • Der Service hängt an Netzwerkressourcen (DB, REST, Message Broker, Fileshares).
  • Der Betrieb fordert planbare Wartungsfenster (Reboots, Updates, Failover).

Der Mehrwert ist nicht „Eleganz“, sondern Betriebssicherheit: weniger harte Prozessabbrüche, weniger inkonsistente Zwischenzustände, weniger manuelle Eingriffe.

Praktiskie slazdi: Was im Shutdown gern schiefgeht

1) Stop wird gesetzt, aber neue Jobs kommen trotzdem rein

Wenn du eingehende Arbeit annimmst (z. B. per Socket, Datei-Trigger, Timer), musst du im Stop-Pfad zuerst die Annahme neuer Arbeit stoppen: Listener schließen, Timer deaktivieren, Scheduler anhalten. Sonst rennst du dem Ende hinterher, weil immer noch neue Jobs starten.

2) Cleanup blockiert (Flush, Close, Finalize)

„Nur noch schnell alles flushen“ kann im Service-Kontext gefährlich sein, wenn das Ziel (Netzlaufwerk, Remote-Log, DB) gerade hängt. Deshalb: Cleanup ja, aber mit begrenzter Zeit. Notfalls musst du entscheiden, welche Daten du in-memory verlierst, statt den kompletten Stop zu blockieren.

3) Locks und Reihenfolge

Pārtraukšanas (Stop) laikā tu bieži piekļūsti tām pašām datu struktūrām kā Worker (Queues, Caches, States). Ja Stop-Thread tur Lock un pēc tam gaida Worker pabeigšanu, kamēr Worker vajag to pašu Lock, rodas Stop-Deadlock. Pretpasākumi: samazināt Lock turēšanas laikus, Stop-ceļā neveikt „gaidīšanu zem Lock“, definēt skaidru secību.

4) Vienlaicība dubultā Stop gadījumā

Praksē Stop var tikt aktivizēts vairākas reizes (piem., Stop + Shutdown vai Stop tiek izsaukts atkārtoti). Tavs Stop-ceļš jāveido kā idempotents: Stop-Event uzstādīšana ir pieņemama, bet dubultai Join/Free loģikai jābūt droši aizsargātai (piem., ar atomisku flagu).

Operatīvais skats: ko administratori un IT vadītāji sagaida no servisa

Operācijām un administrācijai galvenais nav tas, cik «skaists» ir kods, bet vai pakalpojums:

  • pie Stop uzticami beidzas (plānojami, bez iestrēgumiem),
  • pie Stop neražo inkonsistentus datus (piem., daļēji ierakstīti faili, atvērtas transakcijas),
  • kļūmes gadījumā sniedz lietojamus logus,
  • apkopes logu un izvietošanas (deployments) laikā ir paredzams.

Tāpēc Stop-Timeout nav tikai „izstrādātāja lieta“: tas ietekmē patchu ciklus, atjaunošanās laikus un to, vai automatizētas izvietošanas vispār ir iespējamas.

Konkrētas vadlīnijas robustam Shutdown-dizainam

Ja vēlies tēmu pragmatiski standardizēt, šīs vadlīnijas ir sevi attaisnojušas:

  • Viens globāls Stop-Event, Manual Reset, izveidots agri servisā un atbrīvots vēlu.
  • Bez Sleep worker-loopos bez Stop-draudzīgas alternatīvas (Wait ar timeout).
  • Visi ārējie izsaukumi ar timeouts (DB, HTTP, Fileshares). Timeouts jāizvēlas tā, lai tie iederas tavā shutdown-timeout.
  • Stop-Timeout kā konfigurācija (piem., INI/Registry), lai operācijas var reaģēt bez nepieciešamības rekompilēt.
  • Pakāpju modelis: vispirms graceful (esošās darba vienības pabeigt), pēc tam izvēles kārtībā „soft abort“ (neuzsākt jaunus soļus), beigās kā pēdējais līdzeklis ciets Exit.
  • Labi Stop-logi ar fāzēm un laika mērījumiem.

Secinājums: TEvent + Stop-Timeout nav greznība, bet pārvaldāmība

Iesprūdis Stop parasti nav vienkāršs atsevišķs kļūdas gadījums — biežāk tas ir arhitektūras caurums: darbs notiek threados vai blokējošos izsaukumos, kuri nezin par kopīgu Stop-signālu. Ar skaidru Stop-Event (TEvent, Manual Reset), stop-draudzīgām Wait vietā Sleep izmantošanu, konsekventiem timeouts ārējām atkarībām un definētu shutdown-timeout tu iegūsti servisu, kas ikdienā ir paredzams.

Šāds kods atmaksājas sevi jo īpaši tad, ja tavs serviss darbojas produkcijā ar apkopes logiem, automatizētām izvietošanām vai kritiskām blakussekām. Tad „Graceful Shutdown“ nav kosmētika, bet komponents stabilai darbībai un mazākām eskalācijām nākamā reboot laikā.

Ja vēlies jūsu Stop-ceļu vienreiz pareizi uzbūvēt vai pārbaudīt esošu Delphi-servisu attiecībā uz robustu shutdown-loģiku un darbības drošību, tehnisks sparrings-call bieži ir ātrākais ceļš uz konkrētiem pasākumiem: Sazināties.

Šajā tēmā arī Delphi Windows Service un Tevent Delphi ir svarīgi. Raksts saprotami izkārto šos aspektus un rāda, kam ikdienā jāpievērš uzmanība.

Projektu vai modernizācijas ieceri apspriest ar Net-Base.

Nākamais solis

Ja no tēmas rodas reāls projekts, arhitektūru, esošo sistēmu un ekspluatāciju jāvērtē kopā jau agrīnā posmā.

Mēs atbalstām ne tikai atsevišķu jautājumu risināšanā, bet arī tad, kad no avota koda fragmentiem, mantojuma sistēmu jautājumiem vai portāla idejām jāizveido stabils uzņēmuma līmeņa projekts.

  • Esošais stāvoklis, mērķa stāvoklis un tehniskie riski tiek kopīgi vērtēti.
  • REST, datu piekļuve, portāli un Rollout netiek pārcelti uz vēlākām fāzēm.
  • Jūs laikus redzat, kurš risinājums ir ekonomiski un darbības ziņā dzīvotspējīgs.

Kopīgot ierakstu

Kopīgot šo ierakstu tieši

LinkedIn, X, XING, Facebook, WhatsApp un e-pasts ir nekavējoties pieejami. Instagramam mēs tūlīt sagatavojam saiti un īsu tekstu.

E-pasts

Instagram atveras jaunā cilnē. Saite un īss teksts tiek iepriekš nokopēti starpliktuvē.