Net-Base Revista

02.08.2026

Windows Serviço em Delphi: Implementar corretamente o encerramento gracioso com TEvent e timeout de parada

Quando um serviço Windows trava ao parar, raramente é por acaso: na maioria dos casos threads, I/O ou loops de Sleep bloqueiam sem um caminho de cancelamento. Este artigo prático mostra como, em Delphi, implementar com TEvent um Graceful Shutdown limpo e como tratar corretamente Stop-Timeouts...

02.08.2026

Do tema da revista à prática do projeto

Páginas de serviços e técnicas correspondentes ao artigo

Um Windows Service in Delphi tende, no dia a dia, a ser discreto: roda em segundo plano, processa jobs, grava logs, comunica-se com bases de dados ou com APIs REST. Até que alguém clique em “Dienst beenden” — ou que seja necessário um patch-reboot — e o serviço não pare de forma limpa. Então o console de serviços mostra durante minutos “Wird beendet…”, o serviço fica no estado Stop Pending, e, no pior cenário, o processo é terminado à força. É justamente aí que vale a pena tratar o Windows Service in Delphi Graceful Shutdown como um tema arquitetural consciente: com um sinal de shutdown claro, timeouts definidos e threads que realmente reagem.

Este artigo não aborda internas de frameworks, mas propõe um padrão aplicável na prática: TEvent como sinal de stop (um objeto de sincronização próximo ao kernel de System.SyncObjs), combinado com uma estratégia de Stop-Timeout que contemple tanto o Windows Service Control Manager (SCM, ou seja, o componente Windows que inicia/pára serviços) quanto os seus próprios worker-threads. Inclui casos típicos de borda, abordagens de debugging e a questão de quando a lógica adicional realmente compensa.

Windows Service in Delphi Graceful Shutdown na prática

A causa mais comum é simples: o serviço tem pelo menos uma thread que está presa numa operação bloqueante e não conhece um caminho de cancelamento. Clássicos:

  • Laços de polling com Sleep: „while not Terminated do Sleep(1000)“. Ao receber o stop, o sinal chega, mas a thread só reage após até 1 segundo (ou 30 segundos…).
  • I/O bloqueante: chamadas a bases de dados, requisições HTTP, named pipes, esperas no sistema de ficheiros — tudo o que „apenas espera“ sem observar um sinal de stop.
  • Consumidor de fila sem mecanismo de wakeup: um worker espera numa fila, mas ao parar ele não é acordado para sair.
  • Ordem de locks / deadlocks: no caminho de stop executa-se „cleanup“ enquanto outras threads ainda detêm locks. Isso costuma ocorrer apenas no caminho de parada, porque a ordem ali difere do funcionamento normal.

O Windows SCM espera que um serviço reaja rapidamente a um comando de stop e reporte continuamente seu status (via SetServiceStatus; Delphi encapsula isso no componente de serviço). Se você aceita o evento de stop, mas não encerra suas threads corretamente, o processo permanece vivo — e o Windows acaba por decidir, em algum momento, que „está demorando demais“. O resultado é então ou uma finalização forçada ou um serviço preso numa zona intermédia pouco clara.

Princípio básico: um sinal de stop que todo worker entenda

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Quando os workers aguardam por „Work ou Stop“, a latência de stop cai sem polling.

Um encerramento ordenado funciona apenas se você tiver um sinal que:

  • possa ser observado por todas as threads relevantes,
  • também a partir de estados de espera bloqueantes surta efeito,
  • seja determinístico no caminho de parada (sem a esperança „talvez ele saia algum dia“),
  • tenha uma clara Timeout-Strategie.

Em Delphi o TEvent é uma ferramenta muito útil para isso: um objeto de event que internamente é implementado sobre Windows-handles (comparável a CreateEvent/SetEvent). Você pode usá‑lo como sinal „Stop requested“. Cada worker então não espera cegamente, mas espera „por trabalho ou por Stop“.

Escolher corretamente o TEvent: ManualReset vs. AutoReset

Para sinais de parada normalmente você quer Manual Reset (reset manual): uma vez determinado, o event permanece „signaled“ até que você o resete. Isso garante que qualquer thread que entre posteriormente em uma fase de espera ainda reconheça o sinal de parada. Auto Reset seria arriscado aqui, porque reseta o sinal automaticamente após um thread em espera e outros threads poderiam perder o sinal de parada.

Delphi-Service-Lebenszyklus: Onde o Stop realmente chega

Um Delphi-Windows- e Linux-Services baseia‑se tipicamente em TService (VCL/RTL). O SCM envia comandos (Start, Stop, Pause, Continue). Delphi então invoca os eventos/métodos correspondentes (dependendo do template, ex.: OnStart, OnStop, OnExecute).

Importante para a arquitetura:

  • OnStop não é lugar para esperar longamente sem atualizações de estado. É o ponto onde você inicia o shutdown e então espera de forma controlada — com timeout.
  • OnExecute costuma ser um loop. Se você trabalha ali de forma „infinita“, o loop precisa reagir ao sinal de Stop.
  • Worker-Threads (TThread ou pools de threads) devem reagir ao mesmo sinal de Stop; caso contrário o serviço estará logicamente parado, mas fisicamente ainda não concluído.

Padrão limpo: Stop-Event + Join dos Worker + fallback forçado

O padrão aplicável na prática consiste em quatro passos:

  1. Solicitar parada: setar o Stop-Event, não aceitar novos jobs.
  2. Disparar wakeups: se os workers estiverem esperando em filas ou em sleep, eles precisam poder „acordar“ (por exemplo via sinal de Event/Queue).
  3. Encerrar ordenadamente: os workers terminam seus loops, fecham recursos (conexões DB, arquivos, handles) e reportam „concluído“.
  4. Timeout e fallback: se nem tudo terminar a tempo, você precisa decidir: esperar mais (com atualização de status) ou abortar controladamente/encerrar forçadamente (conforme o risco).

O cerne é: nenhum thread deve esperar exclusivamente por tempo (Sleep) ou bloquear exclusivamente em I/O sem, em paralelo, considerar um sinal de Stop. Em vez disso, trabalhe com funções de espera que considerem múltiplos sinais (por exemplo, „Stop-Event ou Work-Event“), ou encapsule o I/O em timeouts mais verificações de Stop.

Pensar corretamente o Stop-Timeout: SCM-Timeout vs. timeout de shutdown próprio

Aqui ocorrem a maioria dos mal-entendidos em projetos. Existem dois níveis diferentes de timeout:

  • Expectativa do SCM: Windows espera que você informe progresso regularmente enquanto estiver no estado SERVICE_STOP_PENDING. Caso contrário, parece que você travou. Delphi cuida disso em parte, mas assim que você ficar bloqueado por mais tempo, precisa de uma estratégia para continuar permitindo atualizações de status (ou para manter sua fase de parada curta).
  • Seu próprio timeout de shutdown: Você define, por exemplo, „Damos 20 segundos para encerrar jobs em execução de forma limpa, depois abortamos.“ Isso é uma decisão de arquitetura: consistência de dados vs. imposição de reboot vs. requisitos operacionais.

Na prática isso significa: seu serviço deve rapidamente entrar em um estado no qual ele não inicia novas unidades de trabalho, e então apenas espera que o trabalho em execução termine — mas não indefinidamente. E essa fase de espera deve ocorrer em pequenos intervalos, para que você possa reagir e, se necessário, registrar logs.

Quanto tempo a parada pode durar?

Não existe um número mágico que sirva sempre. Para muitos serviços de negócio, uma faixa alvo de 5–30 segundos é realista: tempo suficiente para dados „in-flight“, mas curto o bastante para janelas de patch. Se você precisa regularmente de mais tempo, isso costuma indicar que você processa unidades muito grandes de uma vez ou que dependências externas (DB/HTTP) estão sem timeout.

Implementação com TEvent: arquitetura que se mantém estável em operação

Uma arquitetura comprovada no serviço Delphi é a seguinte (sem exaurir detalhes de framework):

  • Um Stop-Event (TEvent, Manual Reset), que é acionado ao parar.
  • Um ou mais Worker-Threads, que em seu loop principal verificam regularmente o Stop.
  • Opcionalmente um Work-Event ou uma fila que sinaliza trabalho. Os workers então esperam por „Work ou Stop“.
  • Uma Shutdown-Phase que faz o join dos workers (ou seja, espera até que terminem), mas com timeout.

O que importa não é se você usa TThread, omnithreadlibrary ou um pool próprio, mas sim que seus workers não executem „às cegas“. Um loop de worker deve estruturalmente se parecer com: Aguardar evento(s) → Trabalho em pequenos blocos → entre blocos verificar Stop → liberar recursos de forma limpa.

Armadilha: apenas Terminate não basta

Muitos threads do Delphi são interrompidos com Terminate. Mas isso é apenas uma flag. Se o thread estiver atualmente em uma API bloqueante, nada acontece imediatamente. Por isso um Stop-Event próprio é tão útil: você pode integrá-lo em chamadas de espera e desencadear wakeups direcionados.

Armadilha: FreeOnTerminate no contexto de Service

Em services costuma-se ver FreeOnTerminate := True. Isso pode funcionar, mas torna o shutdown mais difícil de controlar, pois você frequentemente deixa de ter uma referência limpa para aguardar o fim do thread e registrar estados de erro. Para uma lógica de parada controlada é geralmente mais estável possuir explicitamente os threads e, no shutdown, esperar e liberar de forma determinística.

Operações bloqueantes: como torná-las interrompíveis

Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
I/O bloqueante sem timeout é a causa mais frequente de travamentos de parada no serviço.

A parte complicada não é o evento em si, mas os pontos onde seu serviço bloqueia. Três classes típicas:

1) Substituir Sleep/Polling: Wait com Stop-Event

Se você opera periodicamente („verificar a cada 10 segundos“), não use Sleep(10000); espere por um event com timeout. Assim, seu Stop-Event pode encerrar a espera imediatamente. Isso reduz a latência de parada e evita a sensação de „o serviço não responde“.

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

Se você tem uma arquitetura produtor/consumidor (por exemplo, jobs colocados em uma fila), precisa de um sinal que acorde os consumidores. Frequentemente isso é outro TEvent (Work available). O consumidor então espera por dois handles: „Work“ ou „Stop“. Ao parar, você seta o Stop-Event e, se necessário, também o Work-Event, para garantir que todos os consumidores saiam do Wait.

3) Chamadas externas (DB/HTTP): timeouts e caminhos de cancelamento

Em acessos a banco de dados ou chamadas HTTP decide-se se seu serviço encerra de forma limpa. Para operação vale: Nenhuma chamada sem timeout. Um timeout não é luxo, é pré-requisito para controlabilidade. Além disso, você deve verificar o Stop entre fases de retries/backoff. Caso contrário, tem-se o clássico: „o serviço não para porque está fazendo 10 retries com Sleep“.

Em algumas bibliotecas você pode disparar cancelamentos explicitamente (por exemplo, cancelamento de query). Se isso não for possível, ao menos configure timeouts curtos o suficiente para não extrapolarem o timeout de desligamento.

Stop Pending corretamente: Status, Logging und Erwartungsmanagement

Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
Logs por fase e medições de tempo tornam Stop-Timeouts reproduzíveis e explicáveis.

Quando um serviço para, é importante do ponto de vista operacional entender onde ele está preso. Para isso você precisa de duas coisas:

  • Marcadores de log no caminho de parada: „Parada solicitada“, „nenhum novo job“, „aguardando workers“, „Worker X finalizado“, „shutdown concluído“.
  • Tempos mensuráveis: Quanto tempo dura a parada? Qual fase consome tempo? Muitas vezes já basta uma medida de tempo monótona como GetTickCount64 ou TStopwatch (monótono = não distorcido por alterações do relógio do sistema).

Se, no caminho de desligamento, você escrever apenas uma única entrada de log „Stopping…“, a depuração em campo permanece um jogo de adivinhação. Na operação do serviço, os logs frequentemente são a única coisa que você recebe sem interação.

Quais Logs são realmente úteis em serviços?

  • PID do serviço, hora de início, versão/build (sem overhead excessivo).
  • Número de Worker ativos, número de jobs in-flight.
  • Dependências externas ativas: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (apenas agregado, não cada detalhe).
  • Timeout de parada atingido: quais Worker ainda estão pendentes?

Depuração em campo: tornar reproduzível em vez de adivinhar

Os problemas de parada costumam ocorrer apenas em produção: carga diferente, latências diferentes, permissões diferentes, janelas de patch diferentes. Alguns passos práticos:

Testar o serviço sob controle

  • Parada durante processamento ativo (não em idle).
  • Parada durante falha externa: DB brevemente inacessível, endpoint HTTP lento, compartilhamento de arquivos indisponível.
  • Parada imediatamente após a inicialização (race conditions: Worker ainda em construção).

Sinais do Event Viewer e do Service Control Manager

Windows escreve eventos de serviço, mas eles são frequentemente de alto nível. É melhor quando seu serviço escreve por si só em um arquivo de log ou no Event Log do Windows. Importante: o logging deve continuar funcionando no caminho de desligamento. Se você liberar o logger no shutdown cedo demais ou o flush ficar bloqueado, você perde exatamente os rastros decisivos.

Tornar threads pendentes visíveis

Se você vir repetidamente „Stop Timeout“, vale a pena verificar os estados das threads (por exemplo, via Debugger/Procdump em ambiente de teste). Frequentemente você encontrará uma thread em estado de espera por um handle que nunca é sinalizado, ou em uma chamada de rede sem timeout. A correção raramente é „ainda mais sleep“, mas sim um caminho de abortamento limpo.

Quando o esforço realmente compensa?

Um serviço minimalista que só tem um timer e nenhuma dependência externa pode às vezes „parar simplesmente“. Mas assim que uma das condições a seguir se aplica, um Graceful Shutdown bem implementado quase sempre compensa:

  • O serviço processa jobs com efeitos colaterais (escrita de arquivos, transações DB, chamadas de API).
  • Existem várias threads ou um pool.
  • O serviço depende de recursos de rede (DB, REST, Message Broker, compartilhamentos de arquivos).
  • A operação exige janelas de manutenção programáveis (reboots, updates, failover).

O valor não é “elegância”, mas segurança operacional: menos terminações forçadas de processo, menos estados intermediários inconsistentes, menos intervenções manuais.

Armadilhas práticas: o que costuma dar errado no shutdown

1) A parada é sinalizada, mas novos jobs continuam chegando

Se você aceita trabalho de entrada (por exemplo, via socket, gatilho de arquivo, timer), no caminho de desligamento você deve primeiro parar a aceitação de novo trabalho: fechar listeners, desativar timers, pausar o scheduler. Caso contrário, você corre atrás do fim porque novos jobs continuam sendo iniciados.

2) Cleanup bloqueado (Flush, Close, Finalize)

„Só mais um flush rápido em tudo“ pode ser perigoso no contexto de serviço, se o destino (unidade de rede, log remoto, DB) estiver travado. Por isso: cleanup sim, mas com tempo limitado. Se necessário, você deve decidir quais dados em memória perderá, em vez de bloquear completamente a parada.

3) Locks und Reihenfolge

Beim Stop greifst du häufig auf dieselben Datenstrukturen zu wie die Worker (Queues, Caches, States). Wenn der Stop-Thread Locks hält und dann auf Worker-Ende wartet, während Worker denselben Lock brauchen, hast du einen Stop-Deadlock. Gegenmittel: Lock-Hold-Zeiten klein halten, im Stop-Pfad nicht „unter Lock warten“, klare Reihenfolge definieren.

4) Nebenläufigkeit beim doppelten Stop

In der Praxis kann Stop mehrfach getriggert werden (z. B. Stop + Shutdown, oder Stop kommt erneut). Dein Stop-Pfad sollte idempotent sein: Stop-Event setzen ist okay, aber doppelte Join/Free-Logik muss sauber geschützt werden (z. B. über ein Atomik-Flag).

Operativer Blick: Was Admins und IT-Leads vom Service erwarten

Für Betrieb und Administration zählt am Ende nicht, wie „schön“ der Code ist, sondern ob der Dienst:

  • bei Stop verlässlich endet (planbar, ohne Hänger),
  • bei Stop keine inkonsistenten Daten produziert (z. B. halbe Dateien, offene Transaktionen),
  • im Fehlerfall brauchbare Logs liefert,
  • bei Wartungsfenstern und Deployments berechenbar ist.

Das ist auch der Grund, warum das Thema Stop-Timeout nicht nur „Entwicklerkram“ ist: Es beeinflusst Patchzyklen, Recovery-Zeiten und die Frage, ob automatisierte Deployments überhaupt möglich sind.

Konkrete Leitplanken für ein robustes Shutdown-Design

Wenn du das Thema pragmatisch standardisieren willst, haben sich diese Leitplanken bewährt:

  • Ein globales Stop-Event, Manual Reset, früh im Service-Lebenszyklus erstellt, spät freigegeben.
  • Kein Sleep in Worker-Loops ohne Stop-fähige Alternative (Wait mit Timeout).
  • Alle externen Calls mit Timeouts (DB, HTTP, Fileshares). Timeouts so wählen, dass sie in deinen Shutdown-Timeout passen.
  • Stop-Timeout als Konfiguration (z. B. in INI/Registry), damit Betrieb reagieren kann, ohne neu zu kompilieren.
  • Stufenmodell: Erst graceful (laufende Jobs zu Ende), dann optional „soft abort“ (keine neuen Schritte), dann harter Exit als letzter Ausweg.
  • Gute Stop-Logs mit Phasen und Zeitmessung.

Fazit: TEvent + Stop-Timeout ist kein Luxus, sondern Steuerbarkeit

Ein hängender Stop ist selten ein Einzelfehler – meist ist es ein Architekturloch: Arbeit läuft in Threads oder blockierenden Calls, die kein gemeinsames Stop-Signal kennen. Mit einem klaren Stop-Event (TEvent, Manual Reset), stop-fähigen Waits statt Sleep, konsequenten Timeouts für externe Abhängigkeiten und einem definierten Shutdown-Timeout bekommst du einen Service, der im Alltag berechenbar ist.

Der Code lohnt sich besonders, wenn dein Service in Produktionsumgebungen mit Wartungsfenstern, automatisierten Deployments oder kritischen Seiteneffekten arbeitet. Dann ist „Graceful Shutdown“ nicht Kosmetik, sondern ein Baustein für stabilen Betrieb und weniger Eskalationen beim nächsten Reboot.

Wenn du euren Stop-Pfad einmal sauber aufsetzen oder einen bestehenden Delphi-Service auf robuste Shutdown-Logik und Betriebssicherheit überprüfen willst, ist ein technischer Sparrings-Call oft der schnellste Weg zu klaren Maßnahmen: Kontakt aufnehmen.

Für dieses Thema sind auch Delphi Windows Service und Tevent Delphi wichtig. Der Beitrag ordnet diese Aspekte verständlich ein und zeigt, worauf es im Alltag ankommt.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

Próximo passo

Quando o tema se tornar um projeto real, arquitetura, ambiente existente e operação devem ser considerados em conjunto desde o início.

Não apenas apoiamos questões pontuais, mas também quando fragmentos de código-fonte, temas legados ou ideias de portais precisam evoluir para um projeto empresarial robusto.

  • Estado atual, estado-alvo e riscos técnicos são avaliados em conjunto.
  • REST, o acesso a dados, os portais e o Rollout não são adiados para fases posteriores.
  • Você identifica cedo qual caminho é viável econômica e operacionalmente.

Partilhar publicação

Compartilhar esta publicação diretamente

LinkedIn, X, XING, Facebook, WhatsApp e E‑Mail estão disponíveis imediatamente. Para o Instagram, preparamos diretamente o link e o texto curto.

E-mail

O Instagram abre numa nova aba. O link e o texto curto são copiados previamente para a área de transferência.