Net-Base Ajakiri

02.08.2026

Windows teenus Delphi: TEventi ja Stop-Timeouti abil sujuva sulgemise korrektne rakendamine

Kui Windows-teenus peatamisel 'hangub', on see harva juhus: enamasti blokeerivad lõimed, I/O või Sleep-tsüklid, millel puudub katkestusvõimalus. See praktiline artikkel näitab, kuidas Delphi-s TEventi abil puhta graceful shutdowni rakendada ja stop-timeout'e korrektselt käsitleda.

02.08.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

Üks Windows Service in Delphi tundub igapäevaselt sageli tagasihoidlik: töötab taustal, töötleb job’e, kirjutab logisid, suhtleb andmebaaside või REST-API-dega. Kuni keegi klõpsab „Peata teenus“ — või tuleb patch-reboot — ja teenus ei peatu korrektselt. Siis kuvab teenuste konsool mitu minutit „Wird beendet…“, teenus jääb olekusse Stop Pending ja halvimal juhul lõpetatakse protsess jõuliselt. Just siin on mõistlik käsitleda Windows Service in Delphi Graceful Shutdown kui teadlikku arhitektuuriküsimust: selge shutdown-signaal, määratletud time-out’id ja thread’id, mis tõepoolest reageerivad.

Selles kirjutises ei käsitleta raamistikusiseseid detaile, vaid praktilist mustrit: TEvent kui stop-signaal (tuumikuga lähedane sünkroniseerimisobjekt System.SyncObjs-ist), kombineerituna Stop-Timeout-strateegiaga, mis arvestab nii Windows Service Control Manageri (SCM, ehk Windows-komponent, mis teenuseid käivitab/peatab) kui ka sinu enda worker-thread’idega. Lisaks tüüpilised äärmised juhtumid, silumine ja küsimus, millal lisaloogika ennast tegelikult õigustab.

Windows Service in Delphi Graceful Shutdown praktikas

Levinum põhjus on lihtne: teenusel on vähemalt üks thread, mis on kinni blokeerivas operatsioonis ja millel pole katkestusteed. Klassikad:

  • Polling-tsüklid koos Sleep: „while not Terminated do Sleep(1000)“. Peatamisel jõuab signaal kohale, kuid thread reageerib alles kuni 1 sekundi (või 30 sekundi…) pärast.
  • Blokeeriv I/O: andmebaasikutsed, HTTP-päringud, named pipes, faili- või süsteemiootused — kõik, mis „lihtsalt ootab“, ilma et peatamissignaali arvestataks.
  • Järjekonna-tarbija ilma äratamiseta: worker ootab queue’d, aga peatamisel teda ei äratata, et väljuda.
  • Lukustusjärjestus / deadlock’id: peatamisel tehakse „cleanup“, samal ajal kui teised thread’id hoiab lukke. See ilmneb sageli ainult peatamisrajalt, sest seal on lukustamise järjekord erinev kui normaaloperatsioonis.

Windows SCM eeldab, et teenus reageerib Stop-käsule kiiresti ja teatab oma olekust pidevalt (via SetServiceStatus; Delphi kapseldab selle teenusekomponendis). Kui sa küll vastu võtad Stop-sündmuse, aga ei shut down’i oma thread’e puhtalt, jääb protsess elama — ja Windows otsustab mingil hetkel, et see võtab „liiga kaua“. Tulemuseks on kas jõuline lõpetus või teenus, mis jääb ebaselgesse vaheolekusse kinni.

Grundprinzip: Ein Stop-Signal, das jeder Worker versteht

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Kui worker’id ootavad „tööd või peatamist“, väheneb peatamise latentsus ilma polling’uta.

Üks Graceful Shutdown töötab ainult siis, kui sul on signaal, mis:

  • et seda kõik asjakohased thread’id jälgida suudaksid,
  • avalub ka blokeerivates ooteolekutes,
  • Stop-teekonnal on deterministlik (puudub „võib-olla ta kunagi välja tuleb“ lootus),
  • selge Timeout-Strategie on.

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

Bei Stop-Signalen willst du üblicherweise Manual Reset (manuell zurücksetzbar): Einmal gesetzt, bleibt das Event „signaled“, bis du es zurücksetzt. Damit ist sichergestellt, dass jeder Thread, der später in eine Wartephase kommt, das Stop-Signal trotzdem erkennt. Auto Reset wäre hier riskant, weil es das Signal nach einem wartenden Thread automatisch zurücksetzt und andere Threads das Stop-Signal verpassen könnten.

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).

Wichtig für die Architektur:

  • OnStop ist kein Ort für langes Warten ohne Status-Updates. Es ist der Ort, an dem du den Shutdown anstößt und dann kontrolliert wartest – mit Timeout.
  • OnExecute ist oft eine Schleife. Wenn du dort „endlos“ arbeitest, muss die Schleife auf ein Stop-Signal reagieren.
  • Worker-Threads (TThread oder Thread-Pools) müssen auf dasselbe Stop-Signal reagieren, sonst ist der Service logisch gestoppt, aber physisch noch nicht fertig.

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

Das praxistaugliche Muster besteht aus vier Schritten:

  1. Stop anfordern: Stop-Event setzen, keine neuen Jobs mehr annehmen.
  2. Wakeups auslösen: Wenn Worker auf Queues oder Sleeps warten, müssen sie „aufwachen“ können (z. B. über Event/Queue-Signal).
  3. Geordnet beenden: Worker beenden ihre Loops, schließen Ressourcen (DB-Verbindungen, Dateien, Handles) und melden „fertig“.
  4. Timeout und Fallback: Wenn nicht alles rechtzeitig endet, musst du eine Entscheidung treffen: weiter warten (mit Status-Update) oder kontrolliert abbrechen/hart beenden (je nach Risiko).

Der Kern ist: kein Thread darf ausschließlich auf Zeit warten (Sleep) oder ausschließlich auf I/O blockieren, ohne parallel ein Stop-Signal zu berücksichtigen. Stattdessen arbeitest du mit Wartefunktionen, die mehrere Signale berücksichtigen (z. B. „Stop-Event oder Work-Event“), oder du kapselst I/O in Timeouts plus Stop-Checks.

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

Hier passieren in Projekten die meisten Missverständnisse. Es gibt zwei verschiedene Timeout-Ebenen:

  • SCM-Ootus: Windows eeldab, et sa teavitad staatuse SERVICE_STOP_PENDING ajal korrapäraselt edenemisest. Vastasel juhul tundub, et oled kinni jäänud. Delphi hoolitseb selle osa eest osaliselt, kuid kui sa ise pikemalt blokeerid, vajad strateegiat, kuidas edaspidi staatuseuuendusi võimaldada (või kuidas hoida oma peatamisfaas lühike).
  • Sinu enda sulgemise ajapiirang: Sa määratled nt „Anname endale 20 sekundit, et jooksvaid töid korralikult lõpetada, siis katkestame.“ See on arhitektuuriline otsus: andmete konsistentsus vs sunnitud taaskäivitus vs operatiivnõuded.
  • Praktiliselt tähendab see: su teenus peaks kiiresti jõudma sellesse seisundisse, kus ta ei käivita enam uusi tööüksusi, ja siis ainult ootab, et jooksvad tööd lõppeksid – aga mitte lõputult. Ja see ootamisfaas peaks toimuma väikestes intervallides, et sa saaksid reageerida ja vajadusel logida.

    Kui kaua võib peatus kesta?

    Pole olemas üht maagilist numbrit, mis alati sobib. Paljude äriteenuste puhul on realistlik sihtvahemik 5–30 sekundit: piisav aeg jooksvate andmete jaoks, kuid piisavalt lühike uuendustööde akna jaoks. Kui sul kulub regulaarselt kauem, viitab see sageli sellele, et töötled liiga suuri ühikuid korraga või et välised sõltuvused (DB/HTTP) töötavad ilma ajapiiranguta.

    Implementierung mit TEvent: Aufbau, der im Betrieb stabil bleibt

    Ein bewährter Aufbau im Delphi-Service sieht so aus (ohne Framework-Details auszureizen):

    • Ein Stop-Event (TEvent, Manual Reset), das beim Stop gesetzt wird.
    • Ein oder mehrere Worker-Threads, die in ihrer Hauptschleife regelmäßig auf Stop prüfen.
    • Optional ein Work-Event oder eine Queue, die Arbeit signalisiert. Worker warten dann auf „Work oder Stop“.
    • Eine Shutdown-Phase, die Worker „joined“ (also wartet, bis sie beendet sind), aber mit Timeout.

    Das Entscheidende ist nicht, ob du TThread, omnithreadlibrary oder einen eigenen Pool nutzt, sondern dass deine Worker nicht „blind“ laufen. Ein Worker-Loop sollte strukturell so aussehen: Warten auf Ereignis(e) → Arbeit in kleinen Chunks → zwischen Chunks Stop prüfen → Ressourcen sauber freigeben.

    Fallstrick: Terminate allein reicht nicht

    Viele Delphi-Threads werden mit Terminate „abgebrochen“. Das ist aber nur ein Flag. Wenn der Thread gerade in einer blockierenden API steckt, passiert erst einmal nichts. Darum ist ein eigenes Stop-Event so hilfreich: Du kannst es in Warteaufrufe integrieren und gezielt Wakeups auslösen.

    Fallstrick: FreeOnTerminate im Service-Kontext

    In Services sieht man oft FreeOnTerminate := True. Das kann funktionieren, macht Shutdown aber schwerer zu kontrollieren, weil du dann häufig keine saubere Referenz mehr hast, um auf Thread-Ende zu warten und Fehlerzustände zu protokollieren. Für kontrollierte Stop-Logik ist es meist stabiler, Threads explizit zu besitzen und im Shutdown deterministisch zu warten und zu freigeben.

    Blockierende Operationen: So machst du sie stop-fähig

    Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
    Blokeeriv I/O ilma Timeout’ita on teenuse peatuse kinnijäämise kõige tavalisem põhjus.

    Raskem osa ei ole sündmus ise, vaid kohad, kus sinu teenus blokeerub. Drei typische Klassen:

    1) Sleep/Polling ersetzen: Wait mit Stop-Event

    Kui töötad perioodiliselt („iga 10 sekundi järel kontroll“), ära kasuta Sleep(10000), vaid oota sündmust timeout’iga. Siis saab sinu Stop-Event ootamise kohe lõpetada. See vähendab Stop-latentsust ja väldib tunnet „Service reagiert nicht“.

    2) Queue-Consumer: Work-Event + Stop-Event kombinieren

    Kui sul on Producer/Consumer-arkitektuur (nt tööd pannakse järjekorda), vajad signaali, mis äratab consumer’id. Sageli on selleks täiendav TEvent (Work available). Consumer ootab siis kahte Handles: „Work“ või „Stop“. Bei Stop’iga seadistad Stop-Eventi ja vajadusel ka Work-Eventi, et kõik consumer’id garanteeritult Wait’ist väljuksid.

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

    Andmebaasi- või HTTP-kutsete puhul otsustub, kas sinu teenus puhtalt lõpetab. Für den Betrieb gilt: Kein Call ohne Timeout. Timeout ei ole luksus, vaid eeldus juhtitavuse jaoks. Zusätzlich solltest du zwischen Retries/Backoff-Phasen immer Stop prüfen. Muidu tekib klassika: „Service stoppt nicht, weil er gerade 10 Retries mit Sleep macht“.

    Mõnedes raamatukogudes saad katkestusi otseselt käivitada (nt Query-Abbruch). Kui seda ei saa teha, pead vähemalt seadistama Timeout’id piisavalt lühikeseks, et need ei ületaks den Shutdown-Timeout.

    Stop Pending korrekt: Status, Logging und Erwartungsmanagement

    Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
    Faasi-logid ja ajamõõtmine teevad Stop-Timeoutid reprodutseeritavaks ja seletatavaks.

    Kui teenus peatub, on operatsiooni vaates oluline mõista, kus ta kinni jääb. Selleks vajad kahte asja:

    • Log-Marker Stop-teekonnas: „Stop angefordert“, „keine neuen Jobs“, „warte auf Worker“, „Worker X beendet“, „Shutdown fertig“.
    • Messbare Zeiten: Kui kaua kestab Stop? Milline faas sööb aega? Sageli piisab monotoonse aja mõõdikust nagu GetTickCount64 või TStopwatch (monotoon = nicht durch Systemzeitänderungen verzerrt).

    Kui sul peatamisrajal (Stop-Pfad) on logisse kirjutatud ainult üks ainus sissekanne „Stopping…“, jääb välitingimustes tõrkeotsing arvuvismiseks. Teenuse käitamisel on logid sageli ainus, mida saad ilma sekkumiseta.

    Millised logid on teenustes tõeliselt kasulikud?

    • Teenuse PID, käivitusaeg, versioon/Build (ilma liigse overhead’ita).
    • Aktiivsete Workerite arv, in-flight tööde arv.
    • Aktiivsed välised sõltuvused: „DB-kõne käib“, „HTTP-päring käib“, „faili flush käib“ (ainult agregeeritult, mitte iga detail).
    • Stop-Timeout saavutatud: millised Workerid on veel avatud?

    Tõrkeotsing välitingimustes: teha reprodutseeritavaks, mitte arvata

    Peatamisprobleemid ilmnevad sageli ainult tootmises: teine koormus, erinevad latentsused, erinevad õigused, erinevad paigaldusaknad. Mõned praxiseriproovitud võtted:

    Teenuse kontrollitud testimine

    • Seiskamine aktiivse töötlemise ajal (mitte ooterežiimis).
    • Seiskamine välishäire ajal: DB ajutiselt mitte kättesaadav, HTTP-endpoint aeglane, failijagamine kadunud.
    • Seiskamine vahetult pärast käivitust (race-conditions: Workerid on veel loomisel).

    Event Vieweri ja Service Control Manageri signaalid

    Windows kirjutab teenuse sündmusi, aga need on sageli karedad. Parem on, kui sinu teenus kirjutab ise logifaili või Windows sündmuslogi. Oluline on: logimine peab peatamisrajal (Stop-Pfad) endiselt töötama. Kui vabastad loggeri shutdown’i ajal liiga vara või kui Flush on blokeeritud, kaotad just need otsustavad jäljed.

    Lõksus olevad Threadid nähtavaks teha

    Kui näed korduvalt „Stop Timeout“, tasub vaadata lõimede (Thread) olekuid (nt Debugger/Procdump testkeskkonnas). Sageli leiad lõime Wait-olekus mõne handle’i peal, mis kunagi ei signaleeru, või võrgu-kõne ilma timeout’ita. Lahendus ei ole tavaliselt „rohkem Sleep’i“, vaid korrektne katkestamistee.

    Millal pingutus tõesti tasub?

    Minimalistlik teenus, millel on ainult üks taimer ja puuduvad välised sõltuvused, võib mõnikord „lihtsalt peatuda“. Kui aga üks järgmistest kehtib, tasub peaaegu alati korralik sujuv lõpetamine (Graceful Shutdown):

    • Teenus töötleb töid kõrvalmõjudega (failide kirjutamine, DB-transaktsioonid, API-kutsed).
    • On mitu lõime või lõimede bassein.
    • Teenus sõltub võrguresurssidest (DB, REST, Message Broker, Fileshares).
    • Ekspluatatsioon nõuab planeeritavaid hooldusaknaid (taaskäivitused, uuendused, failover).

    Kasulik mõju ei ole „ilu“, vaid töökindlus: vähem jõulisi protsessi katkestusi, vähem ebajärjekindlaid vaheolekuid, vähem käsitsi sekkumisi.

    Praktilised lõksud: mis sulgemisel sageli valesti läheb

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

    Kui võtad vastu sissetulevat tööd (nt Socket, failitrigger, Taimer), pead peatamisrajal esmalt peatama uue töö vastuvõtu: listener sulgeda, taimeri keelata, Scheduler peatada. Vastasel juhul jooksed lõpu järel, sest uusi töid käivitatakse endiselt.

    2) Cleanup blockiert (Flush, Close, Finalize)

    „Ainult kiirelt kõik flushida“ võib teenuse kontekstis ohtlik olla, kui sihtkoht (võrguketas, Remote-Log, DB) parajasti hangub. Seetõttu: Cleanup jah, aga piiratud ajaga. Hädaolukorras pead otsustama, milliseid andmeid mälus kaotad, selle asemel et kogu seiskamist blokeerida.

    3) Locks und Reihenfolge

    Stopi puhul kasutad tihti samu andmestruktuure nagu Workerid (Queues, Caches, States). Kui Stop-Thread hoiab lukke ja seejärel ootab Workerite lõppu, samal ajal kui Workerid vajavad sama lukku, tekib Stop-Deadlock. Vastumeetmed: hoida luku hoidmise ajad lühikesed, stop-teekonnas mitte „lukuga oodata“, määratleda selge järjekord.

    4) Kaasajalisus kordse Stop’i korral

    Praktikas võib Stop mitu korda käivituda (nt Stop + Shutdown või Stop kordub). Sinu Stop-teekond peaks olema idempotentne: Stop-sündmuse seadmine on ok, kuid topelt Join/Free-Logik tuleb korrektselt kaitsta (nt atomik-flag’i abil).

    Operatiivne vaade: mida adminid ja IT-juhtid teenuselt ootavad

    Töö ja halduse jaoks ei ole lõppkokkuvõttes oluline, kui „ilus“ kood on, vaid see, kas teenus:

    • Stop’i korral usaldusväärselt lõpeb (ennustatavalt, ilma hangumiseta),
    • Stop’i ajal ei tekita inkonsistentseid andmeid (nt pooleliolevad failid, avatud tehingud),
    • veaolukorras annab kasulikke logisid,
    • hooldusakende ja juurutuste ajal on ennustatav.

    See on ka põhjus, miks Stop-Timeout teema ei ole ainult „arendajate asi“: see mõjutab patchitsükleid, taastumisaegu ja küsimust, kas automatiseeritud juurutused üldse võimalikud on.

    Konkreetsed juhised robustse sulgemisdisaini jaoks

    Kui soovid teemat pragmaatiliselt standardiseerida, on need juhised end õigustanud:

    • Üldine Stop-sündmus (Manual Reset), luua varakult teenuse elutsüklis, vabastada alles hiljem.
    • Ära kasuta Sleep’i Worker-tsüklites ilma Stopi-toega alternatiivita (Wait koos ajapiiranguga).
    • Kõik väliskõned ajapiirangutega (DB, HTTP, failijagamine). Valida ajapiirangud nii, et need mahuksid sinu Shutdown-Timeout’i sisse.
    • Stop-Timeout konfiguratsioonina (nt INI/Registry), et opereerimine saaks reageerida ilma ümberkompileerimata.
    • Astestatud mudel: esmalt graatsiline (jooksvaid ülesandeid lõpetada), siis valikuline „soft abort“ (uute sammude alustamine keelatud), lõpuks kõva väljalülitus kui viimane abinõu.
    • Hea Stop-logimine faaside ja ajamõõtmisega.

    Kokkuvõte: TEvent + Stop-Timeout ei ole luksus, vaid juhtimisvõime

    Hangunud Stop on harva üksikviga – tavaliselt on tegu arhitektuurilise lõhega: töö jookseb lõimestes või blokeerivates kõnedes, mis ei tunne ühist Stop-signaali. Selge Stop-sündmuse (TEvent, Manual Reset), stop-sobivate Wait-idega Sleep’i asemel, järjekindlate ajapiirangutega väliste sõltuvuste jaoks ja defineeritud Shutdown-Timeout’i abil saad teenuse, mis igapäevases kasutuses on ennustatav.

    See lähenemine tasub end eriti ära, kui sinu teenus töötab tootmiskeskkonnas hooldusakende, automatiseeritud juurutuste või kriitiliste kõrvalmõjudega. Siis ei ole „Graceful Shutdown“ kosmeetika, vaid osa stabiilsest opereerimisest ja väiksem risk eskalatsioonideks järgmise taaskäivituse puhul.

    Kui soovite teie Stop-teekonda korrektselt üles seada või üle vaadata olemasolevat Delphi-teenust robusete sulgemisloogika ja töökindluse osas, on tehniline sparringkõne sageli kiireim tee selgeteni meetmeteni: võtke ühendust.

    Selle teema puhul on olulised ka Delphi Windows Service ja Tevent Delphi. Artikkel paigutab need aspektid arusaadavalt ja näitab, millele igapäevases töös tähelepanu pöörata.

    Arutada projekti või moderniseerimisettevõtmist koos Net-Base.

    järgmine samm

    Kui teemast saab reaalne projekt, tuleks arhitektuuri, olemasolevat keskkonda ja ekspluatatsiooni varakult koos vaadelda.

    Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

    • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
    • REST, andmejuurdepääs, portaalid ja juurutamine ei lükata hilisemateks tagajärgedeks edasi.
    • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

    Jaga postitust

    Jaga seda postitust otse

    LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame lingi ja lühiteksti otse ette.

    e-post

    Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.