Net-Base Revista

02.08.2026

Windows Servei a Delphi: Implementar correctament un apagament ordenat amb TEvent i Stop-Timeout

Si un servei Windows es queda penjat en aturar-se, rarament és casualitat: la majoria de vegades són fils, operacions d'E/S o bucles Sleep que bloquegen perquè no disposen d’un mecanisme d'interrupció. Aquest article pràctic mostra com, en Delphi, implementar amb TEvent un tancament ordenat (graceful shutdown) net i com tractar correctament els timeouts d'aturada...

02.08.2026

Del tema de la revista a la pràctica del projecte

Pàgines de serveis i tècniques pertinents per a l'article

Un Windows Service in Delphi sovint sembla poc espectacular en el dia a dia: s’executa en segon pla, processa jobs, escriu logs, accedeix a bases de dades o crida APIs REST. Fins que algú fa clic a “Aturar servei” — o cal aplicar un patch-reboot — i el servei no s’atura netament. Aleshores la consola de serveis mostra durant minuts «Wird beendet…», el servei queda en estat Stop Pending, i en el pitjor dels casos el procés es tanca de forma forçada. Precisament aquí val la pena tractar el Windows Service in Delphi Graceful Shutdown com un tema d’arquitectura conscient: amb un senyal d’aturada clar, timeouts definits i fils que realment hi reaccionen.

En aquest article no es tracta d’internals de framework, sinó d’un patró pràctic: TEvent com a senyal d’aturada (un objecte de sincronització proper al nucli de System.SyncObjs), combinat amb una Stop-Timeout-estratègia que té en compte tant el Windows Service Control Manager (SCM, és a dir, la Windows-component que inicia/atura serveis) com els teus propis fils treballadors. A més, casos límit típics, enfocaments de depuració i la qüestió de quan realment compensa la lògica addicional.

Windows Service in Delphi Graceful Shutdown in der Praxis

La causa més habitual és senzilla: el servei té com a mínim un fil que està en una operació bloquejant i no coneix un camí d’abort. Clàssics:

  • Polling-Schleifen mit Sleep: „while not Terminated do Sleep(1000)“. Beim Stop kommt das Signal an, aber der Thread reagiert erst nach bis zu 1 Sekunde (oder 30 Sekunden…).
  • Blockierende I/O: crides a bases de dades, peticions HTTP, Named Pipes, esperes del sistema de fitxers — tot allò que «simplement espera» sense atendre un senyal d’aturada.
  • Queue-Consumer ohne Wakeup: un worker espera sobre una cua, però en aturar-lo no se’l desperta perquè surti.
  • Lock-Reihenfolge/Deadlocks: en el camí d’aturada es fa «cleanup» mentre altres fils encara tenen locks. Això sol aparèixer només en la ruta d’aturada perquè l’ordre difereix respecte de l’operació normal.

El Windows SCM espera que un servei reaccioni ràpidament a un comandament d’aturada i que informi del seu estat de forma continuada (via SetServiceStatus; Delphi encapsula això en el component de servei). Si reps l’esdeveniment d’aturada però no apagues netament els teus fils, el procés continua viu — i Windows acabarà decidint que «triga massa». El resultat és o bé un tancament forçat o un servei encallat en un estat intermig i indefinit.

Grundprinzip: Ein Stop-Signal, das jeder Worker versteht

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Quan els fils de treball esperen «Treball o Aturar», la latència d’aturada disminueix sense sondeig.

Un Graceful Shutdown només funciona si tens un senyal que:

  • que pugui ser observat per tots els fils rellevants,
  • que també des d‘estats d’espera bloquejants tingui efecte,
  • que en el camí de Stop sigui determinista (sense l’esperança «potser en algun moment en sortirà»),
  • que tingui una clara Timeout-Strategie.

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:

  • Expectativa del SCM: Windows espera que informis periòdicament del progrés mentre estàs en l’estat SERVICE_STOP_PENDING. Si no, donarà la sensació que estàs penjat. Delphi s’encarrega parcialment d’això, però tan bon punt tu mateix et bloquegis més estona, necessites una estratègia per tal de continuar proporcionant actualitzacions d’estat (o per mantenir curta la teva fase d’aturada).
  • El teu propi timeout de apagada: Per exemple, defines „Ens donem 20 segons per tancar netament els jobs en curs, després fem abort.“ Això és una decisió d’arquitectura: consistència de dades vs. forçament de reboot vs. requisits d’operació.
  • Pràcticament això vol dir: el teu servei hauria d’arribar ràpidament a un estat en què no iniciï més unitats de treball, i després només esperi que acabi la feina en curs — però no indefinidament. I aquesta fase d’espera hauria d’executar-se en intervals petits, perquè puguis reaccionar i, si cal, registrar esdeveniments.

    Quant de temps pot durar l’aturada?

    No hi ha cap nombre màgic que valgui sempre. Per a molts serveis de negoci, un rang objectiu de 5–30 segons és realista: temps suficient per a dades „in-flight“, però prou curt per a finestres de patch. Si habitualment necessites més temps, sovint és un indici que processses unitats massa grans d’una sola peça o que depens d’externs (DB/HTTP) que funcionen sense timeout.

    Implementació amb TEvent: estructura que es manté estable en explotació

    Una estructura provada en el servei Delphi és la següent (sense entrar en detalls de framework):

    • Un Stop-Event (TEvent, Manual Reset), que s’estableix a l’aturada.
    • Un o diversos Worker-Threads, que en el seu bucle principal comproven periòdicament l’aturada.
    • Opcionalment un Work-Event o una cua que senyalitzi feina. Els workers esperen llavors „Work o Stop“.
    • Una fase de shutdown, que „uneix“ (és a dir, espera que acabin) els workers, però amb timeout.

    L’important no és si fas servir TThread, omnithreadlibrary o un pool propi, sinó que els teus workers no funcionin «a cegues». Un bucle de worker hauria de tenir estructuralment aquest aspecte: Esperar esdeveniment(s) → Treballar en petits chunks → entre chunks comprovar Stop → alliberar recursos netament.

    Punt crític: Terminate per si sol no n’hi ha prou

    Molts threads de Delphi es cancel·len amb Terminate. Però això és només un flag. Si el thread està ara mateix dins d’una API bloquejant, no passarà res de seguida. Per això un Stop-Event propi és tan útil: el pots integrar en crides d’espera i provocar despertades de manera dirigida.

    Punt crític: FreeOnTerminate := True en el context de servei

    Als serveis sovint es veu FreeOnTerminate := True. Això pot funcionar, però complica el control del shutdown, perquè sovint ja no disposes d’una referència neta per esperar la finalització del thread i registrar estats d’error. Per una lògica de parada controlada és en general més estable posseir els threads de manera explícita i, durant el shutdown, esperar-los i alliberar-los de manera determinista.

    Operacions bloquejants: com fer-les interrompibles

    Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
    L’I/O bloquejant sense timeout és la causa més freqüent dels bloquejos del servei durant la parada.

    La part complicada no és l’esdeveniment en si, sinó els punts on el teu servei es bloqueja. Tres classes típiques:

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

    Si treballes periòdicament („comprovar cada 10 segons“), no facis servir Sleep(10000), sinó espera un esdeveniment amb timeout. Així el teu esdeveniment de parada pot acabar l’espera immediatament. Això redueix la latència d’aturada i evita la sensació „el servei no respon“.

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

    Si tens una arquitectura Producer/Consumer (p. ex. les tasques es posen en una cua), necessites un senyal que desperti els consumidors. Sovint és un altre TEvent (Work available). El consumidor espera llavors dos handles: „Work“ o „Stop“. En el cas d’una parada estableixes l’esdeveniment de parada i, si cal, també l’esdeveniment de treball, perquè tots els consumidors surtin garantidament de l’espera.

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

    En accessos a bases de dades o crides HTTP es decideix si el teu servei s’atura netament. Per a l’explotació val: Cap crida sense timeout. Un timeout no és un luxe, sinó un requisit per a la controlabilitat. A més, entre fases de reintents/backoff sempre has de comprovar l’estat d’aturada. Altrament tens el clàssic: „El servei no s’atura perquè està fent ara mateix 10 reintents amb Sleep“.

    Amb algunes biblioteques pots desencadenar aborts explícitament (p. ex. abortar una query). Si això no és possible, almenys has de configurar els timeouts perquè siguin prou curts i no facin saltar el timeout de shutdown.

    Stop Pending korrekt: Status, Logging und Erwartungsmanagement

    Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
    Registres de fases i mesura del temps fan que els timeouts d’aturada siguin reproduïbles i explicables.

    Quan un servei s’atura, des del punt de vista d’explotació és important entendre on es queda penjat. Per això necessites dues coses:

    • Marcadors al log en el camí d’aturada: „Stop sol·licitat“, „no s’accepten noves tasques“, „esperant workers“, „Worker X finalitzat“, „Shutdown complet”.
    • Mesures temporals: Quant triga l’aturada? Quina fase consumeix més temps? Sovint n’hi ha prou amb una mesura monotònica com GetTickCount64 o TStopwatch (monotònic = no es distorsiona per canvis d’hora del sistema).

    Si en el camí d’aturada només escrius una sola entrada de registre „Stopping…“, la depuració al camp es converteix en un joc d’endevinalles. En funcionament com a servei, els logs sovint són l’únic que obtens sense interacció.

    Quins logs són realment útils en serveis?

    • Service-PID, hora d’inici, versió/build (sense sobrecàrrega innecessària).
    • Nombre de Worker actius, nombre de Jobs en vol.
    • Dependències externes actives: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (només agregat, no cada detall).
    • Arribada del Stop-Timeout: quins Worker continuen oberts?

    Depuració al camp: fer-ho reproducible en lloc d’endevinar

    Els problemes d’aturada acostumen a aparèixer només en producció: altra càrrega, altres latències, altres permisos, altres finestres de patch. Algunes palanques probades en la pràctica:

    Provar el Service sota control

    • Aturada durant el processament actiu (no en idle).
    • Aturada durant una fallada externa: BD breument no disponible, endpoint HTTP lent, compartició de fitxers absent.
    • Aturada immediata després de l’inici (condicions de carrera: Worker encara en desplegament).

    Event Viewer und Service Control Manager Signale

    Windows escriu esdeveniments del servei, però sovint són massa grossos. És millor que el teu Service escrigui ell mateix en un fitxer de log o en l’Event Log de Windows. L’important és: el logging ha d’estar disponible encara en el camí d’aturada. Si allibereixes el logger massa aviat durant el shutdown o el flush queda bloquejat, perds precisament les traces decisives.

    Fer visibles els Threads penjant

    Si veus repetidament «Stop Timeout», val la pena mirar l’estat dels threads (p. ex. via debugger/Procdump en entorn de proves). Sovint trobaràs un thread en estat Wait sobre un handle que mai s’ha senyalitzat, o en una crida de xarxa sense timeout. La solució rarament és «més sleep», sinó un camí d’abort net.

    Quan val la pena l’esforç realment?

    Un servei minimalista que només té un timer i cap dependència externa de vegades pot „parar-se senzillament“. Però en el moment que s’aplica alguna de les condicions següents, una aturada ordenada gairebé sempre compensa:

    • El Service processa Jobs amb efectes secundaris (escriptura de fitxers, transaccions BD, crides d’API).
    • Hi ha diversos Threads o un pool.
    • El Service depèn de recursos de xarxa (BD, REST, brokers de missatges, comparticions de fitxers).
    • El funcionament exigeix finestres de manteniment planificables (reboots, actualitzacions, failover).

    L’avantatge no és la «elegància», sinó la seguretat operativa: menys terminacions dures de procés, menys estats intermedis inconsistents, menys intervencions manuals.

    Falles pràctiques: què falla sovint en el shutdown

    1) S’estableix l’aturada, però continuen entrant nous Jobs

    Si acceptes treball entrant (p. ex. via socket, trigger de fitxer, timer), en el camí d’aturada primer has d’aturar l‘acceptació de nova feina: tancar listeners, desactivar timers, parar el scheduler. Altrament, arribes tard perquè continuen iniciant-se nous Jobs.

    2) El cleanup bloqueja (Flush, Close, Finalize)

    «Cal fer un últim flush de tot» pot ser perillós en context de servei si el destí (unitat de xarxa, log remot, BD) està bloquejat. Per això: fer cleanup, sí, però amb temps limitat. Si cal, has de decidir quina informació perds en memòria en lloc de bloquejar completament l’aturada.

    3) Locks i ordre

    Amb el Stop sovint accedeixes a les mateixes estructures de dades que els Worker (Queues, Caches, States). Si el thread de Stop manté Locks i després espera la fi dels Worker, mentre els Worker necessiten el mateix Lock, tens un Stop-Deadlock. Mesures: mantenir curts els períodes de tinença del Lock, no „esperar sota un Lock“ en el camí de Stop, definir un ordre clar.

    4) Concurrència en el Stop duplicat

    En la pràctica, el Stop pot ser disparat diverses vegades (p. ex. Stop + Shutdown, o el Stop arriba de nou). El teu camí de Stop hauria de ser idempotent: establir el Stop-Event està bé, però la lògica doble de Join/Free ha d’estar ben protegida (p. ex. mitjançant un flag atòmic).

    Visió operativa: què esperen els Admins i els IT-Leads del servei

    Per a l’operació i l’administració, al final no compta com de „bonic“ és el codi, sinó si el servei:

    • a l’hora del Stop s’acaba de manera fiable (planificable, sense penjaments),
    • a l’hora del Stop no produeix dades inconsistents (p. ex. fitxers a mitges, transaccions obertes),
    • en cas d’error proveeix logs útils,
    • en finestres de manteniment i desplegaments és previsible.

    Això és també la raó per la qual el tema del Stop-Timeout no és només „qüestió de desenvolupadors“: afecta els cicles de patch, els temps de recuperació i la pregunta de si els desplegaments automatitzats són realment possibles.

    Directrius concretes per a un disseny de shutdown robust

    Si vols estandarditzar el tema de manera pragmàtica, aquestes directrius han demostrat la seva validesa:

    • Un Stop-Event global, Manual Reset, creat aviat en el cicle de vida del servei i alliberat tard.
    • No Sleep en els bucles dels Worker sense una alternativa que permeti el Stop (Wait amb Timeout).
    • Totes les crides externes amb Timeouts (DB, HTTP, Fileshares). Tria els timeouts perquè encaixin en el teu Shutdown-Timeout.
    • Stop-Timeout com a configuració (p. ex. INI/Registry), perquè l’operació pugui reaccionar sense recompilar.
    • Model per etapes: primer graceful (finalitzar els jobs en execució), després opcional „soft abort“ (no iniciar passos nous), i com a darrera opció un exit forçat.
    • Bons Stop-Logs amb fases i mesura de temps.

    Conclusió: TEvent + Stop-Timeout no és un luxe, sinó control

    Un Stop penjat rarament és un error aïllat: sovint és un forat d’arquitectura: la feina s’executa en threads o en crides bloquejants que no coneixen un senyal de Stop comú. Amb un Stop-Event clar (TEvent, Manual Reset), waits compatibles amb Stop en lloc de Sleep, timeouts coherents per a dependències externes i un Shutdown-Timeout definit, obtens un servei que és previsible en el dia a dia.

    El codi és especialment rendible si el teu servei opera en entorns de producció amb finestres de manteniment, desplegaments automatitzats o efectes secundaris crítics. En aquests casos, el „Graceful Shutdown“ no és cosmètica, sinó un component per a un funcionament estable i menys escalades en el següent reboot.

    Si vols configurar el vostre camí de Stop de forma neta o revisar un servei existent Delphi en relació amb una lògica de shutdown i seguretat operativa robustes, una trucada tècnica de sparring sovint és la via més ràpida cap a mesures concretes: Posa’t en contacte.

    Per a aquest tema també són importants Delphi Windows Service i Tevent Delphi. L’article ordena aquests aspectes de manera entenedora i mostra en què cal fijar-se en el dia a dia.

    Parla d’un projecte o d’una modernització amb Net-Base.

    Pas següent

    Quan d'un tema se'ndevé un projecte real, s'han de considerar aviat i de manera conjunta l'arquitectura, els actius existents i l'operació.

    No només donem suport en qüestions puntuals, sinó també quan, a partir de fragments de codi font, temes de sistemes heredats o idees de portal, ha de sorgir un projecte empresarial sòlid.

    • L'estat actual, la visió objectiu i els riscos tècnics s'avaluen conjuntament.
    • REST, accés a dades, portals i desplegament no es posposaran com a efectes retardats.
    • Veu aviat quin camí és viable des del punt de vista econòmic i operatiu.

    Comparteix la publicació

    Comparteix aquesta publicació directament

    LinkedIn, X, XING, Facebook, WhatsApp i correu electrònic estan disponibles immediatament. Per Instagram preparem l'enllaç i un text curt immediatament.

    Correu electrònic

    Instagram s'obre en una pestanya nova. L'enllaç i el text curt es copien prèviament al porta-retalls.