
Introdução: A verdade sobre múltiplos núcleos
A maioria dos dispositivos modernos — de smartphones a notebooks e servidores — já conta com processadores multicore. No entanto, a grande maioria dos aplicativos ainda opera como se tivesse apenas um único núcleo disponível. Isso acontece porque, por padrão, aplicativos desenvolvidos em Delphi (como em muitas outras linguagens) executam quase toda sua lógica na chamada Main Thread.
Embora o sistema operacional (Windows, Android, iOS etc.) possa distribuir threads entre núcleos, isso só ocorre quando a aplicação cria explicitamente múltiplas threads. Se o código for sequencial e monothread, ele será limitado a um único núcleo — desperdiçando todo o potencial do processador.
"Multithreading allows you to increase the responsiveness of your application and, if your application runs on a multiprocessor or multi-core system, increase its throughput."
"O multithreading permite aumentar a responsividade da sua aplicação e, se ela for executada em um sistema com múltiplos processadores ou núcleos, incrementa o desempenho geral." — Microsoft Docs - Threads and Threading
"Most applications use just a single core and see no speed improvements when run on a multi-core machine. We need to write our programs in a new way."
"A maioria dos aplicativos utiliza apenas um núcleo, sem ganhos de desempenho em máquinas multicore. Precisamos repensar a forma como escrevemos nossos programas." — MSDN Magazine, 2007
Threads no Delphi
Trabalhar com threads no Delphi ou em qualquer outra linguagem exige cuidado. É necessário gerenciar sincronizações, lidar com exceções, manter a responsividade da UI, entre outras questões. Tudo isso pode transformar um código simples em algo muito mais complexo.
"The biggest problem with Thread is that it doesn't enforce the use of any programming patterns. Because of that, you can use it to create parallel programs that are hard to understand, hard to debug, and which work purely by luck. I should know - I shudder every time I have to maintain my old Thread-based code."
"O maior problema com Thread é que ela não impõe o uso de nenhum padrão de programação. Por causa disso, você pode usá-la para criar programas paralelos que são difíceis de entender, difíceis de depurar e que funcionam puramente por sorte. Eu sei bem disso — estremeço toda vez que tenho que dar manutenção ao meu código antigo baseado em Thread." — Primož Gabrijelčič, Delphi High Performance (p. 212)
Por que criamos a TSafeThread4D
O uso de threads em Delphi, embora extremamente poderoso, está longe de ser trivial. Entre os recursos oferecidos pela linguagem, as Threads Anônimas se destacam por sua simplicidade de implementação — e, por isso, tornaram-se a escolha mais comum entre os desenvolvedores para implementar paralelismo de forma rápida.
Contudo, mesmo com essa facilidade, surgem desafios significativos — especialmente quando há a necessidade de interagir com elementos visuais da UI. Um exemplo clássico é o uso do TAniIndicator, que pode parecer travar ou não exibir sua animação de forma fluida durante o processamento em paralelo.
Isso não significa que o TAniIndicator esteja com defeito — o problema geralmente está na forma como o código interage com ele. Imagine um formulário com uma TProgressBar sendo atualizada em tempo real durante a execução de um processo longo, como a inserção de milhares de registros. Você decide encapsular essa lógica em uma Thread Anônima e utilizar TThread.Synchronize para atualizar visualmente a barra e manter a interface reativa. No entanto, percebe que o TAniIndicator — também presente na tela para mostrar que algo está em andamento — não se movimenta de forma fluida ou trava completamente.
O motivo? Tanto o TAniIndicator quanto a TProgressBar são elementos visuais que dependem da Main Thread para serem atualizados. Ao usar Synchronize com frequência dentro do loop, você congestiona a fila de mensagens da UI, gerando uma disputa de sincronização de atualizações visuais na fila da Main Thread.
Resultado: a barra até avança, mas a animação sofre — parecendo que a aplicação travou, mesmo estando funcional.
Como resolver isso? Com uma estrutura que permita isolar o processamento de forma controlada, sincronizando de forma inteligente os momentos de interação com a UI. Foi exatamente com essa motivação que criamos a TSafeThread4D, que internamente utiliza uma Thread Anônima — mas encapsula todo seu funcionamento em uma arquitetura bem definida, segura e fluente.
Vale destacar que o TSafeThread4D não é voltado apenas para aplicações com interface gráfica. Seu uso também é eficaz em rotinas de backend, onde o controle de paralelismo, tratamento de erros e cancelamentos podem ser igualmente críticos.
Diante desses e de outros desafios comuns no desenvolvimento moderno, nasceu a TSafeThread4D — com o objetivo de oferecer um padrão robusto, reutilizável e simples de aplicar para processamento paralelo em Delphi.
Este mecanismo organiza os eventos de uma thread dentro de uma lógica fluida e segmentada, oferecendo estruturas encadeadas, controladas, com cancelamento nativo e interações seguras com a UI — especialmente no Delphi FMX (Windows, Android/iOS), onde esse controle é ainda mais necessário.
TSafeThread4D – Etapas de Execução e Funcionamento
A TSafeThread4D é uma arquitetura de controle de execução paralela com foco em segurança, previsibilidade e fluidez, baseada em etapas encadeadas e isoladas por meio de procedimentos anônimos ou callbacks com contexto (TThreadContext). Cada uma das etapas é opcional — com exceção da WithOnExecute, que é sempre obrigatória.
Essas etapas são configuradas por meio de chamadas encadeadas no estilo fluent interface, por exemplo: TSafeThread4D.New.WithOnExecute(...).WithOnComplete(...)...
A seguir, detalhamos cada uma delas:
WithOnExecute — Obrigatória
Coração da TSafeThread4D.
Onde ocorre a execução da lógica principal, fora da Main Thread.
- Executada em background.
- Não deve acessar diretamente a interface.
- Para interações com a UI, use
TThread.QueueouTThread.Synchronize.
WithOnInitialize
Executada na Main Thread, no início da execução.
- Ideal para preparar a interface.
- Pode desabilitar botões, iniciar
TAniIndicator, limparTMemo, etc.
WithOnInitializeEvent
Versão baseada em TNotifyEvent (Sender: TObject).
- Executada na Main Thread, logo no início.
- Alternativa tradicional ao uso de closure.
WithOnSuccess
Executada na Main Thread, apenas se OnExecute terminar sem erro e sem cancelamento.
- Ideal para aplicar resultados na UI.
- Exemplo: preencher grids, exibir mensagens, atualizar imagens.
WithOnComplete
Executada sempre ao final da thread, com ou sem erro, com ou sem cancelamento — desde que WithCompleteWithError(True) esteja ativado.
- Ideal para finalizar animações, esconder indicadores ou registrar status.
WithOnTerminate
Executada sempre na Main Thread, ao término da execução.
- Excelente para garantir liberação de recursos visuais e reset da UI.
WithOnTerminateEvent
Versão com TNotifyEvent, compatível com handlers padrão.
- Executada no evento
OnTerminatedo objeto thread interno.
WithOnError
Chamado quando ocorre qualquer exceção dentro da thread.
- Recebe o
ErrorMessagee oTThreadContext. - Ideal para logging, exibição de erros, rollback, etc.
WithOnCancel
Chamado somente se a thread for explicitamente cancelada via RequestCancel.
- Executado após a etapa atual ser concluída.
- Permite encerrar o processo com segurança e consistência.
WithCompleteWithError
Controla se o WithOnComplete será chamado mesmo em caso de erro.
- Valor padrão:
False. - Se
True, garante que o processo seja finalizado mesmo após falhas.
WithThreadPriority (Somente no Windows)
Define a prioridade da thread.
- Usa valores do tipo
TThreadPriority:Idle,Lowest,Lower,Normal,Higher,Highest,TimeCritical.
WithThreadName
Nome descritivo da thread para uso em logs, profiling e debug.
WithMeasureTime
É o suporte ao monitoramento de tempo de execução da tarefa. Ao ativar WithMeasureTime(True), o tempo da etapa OnExecute será armazenado automaticamente em Context.ElapsedMilliseconds, permitindo análises como:
MemoMsg.Lines.Add(Format('[Terminate] Thread "%s" (ID: %d) completed.
Elapsed time: %.3f seconds.', [Context.ThreadName, Context.ThreadID,
Context.ElapsedMilliseconds / 1000]));Essa medição é essencial para benchmarking de operações como inserção em banco, processamento de JSON ou comunicação com APIs.
WithThreadId
Identificador numérico auxiliar para identificar múltiplas execuções paralelas.
O exemplo completo e funcional daTSafeThread4D, incluindo o uso doTAniIndicator,TProgressBare atualizações reais de dados, está disponível no GitHub do TheCodeNaked. Acesse, clone e experimente o paralelismo fluente na prática.
Controle Total via Interface Fluente
A construção da thread é feita usando a interface ISafeThread4DParams, com métodos encadeáveis:
procedure TForm1.btnDownloadJSONClick(Sender: TObject);
var
LResult: TDownloadResult;
begin
LResult := TDownloadResult.Create;
DownloadJSONParams := TSafeThread4DParams.New
.WithMeasureTime(True)
.WithOnInitialize(
procedure(Context: TThreadContext)
begin
AniIndicatorJSON.Visible := True;
AniIndicatorJSON.Enabled := True;
TButton(Sender).Enabled := False;
MemoJSON.Lines.Clear;
MemoMsg.Lines.Clear;
MemoMsg.Lines.Add(Format('[Init] Thread "%s" (ID: %d) has
started.', [Context.ThreadName, Context.ThreadID]));
end)
.WithOnExecute(
procedure(Context: TThreadContext)
begin
DownloadJSON(LResult);
end)
.WithOnSuccess(
procedure(Context: TThreadContext)
begin
MemoJSON.Lines.Add(LResult.JSONData);
MemoMsg.Lines.Add(Format('[Success] Thread "%s" (ID: %d)
completed successfully.',
[Context.ThreadName, Context.ThreadID]));
end)
.WithOnTerminate(
procedure(Context: TThreadContext)
begin
AniIndicatorJSON.Enabled := False;
AniIndicatorJSON.Visible := False;
TButton(Sender).Enabled := True;
MemoMsg.Lines.Add(Format('[Terminate] Thread "%s" (ID: %d)
completed. Elapsed time: %.3f seconds.',
[Context.ThreadName, Context.ThreadID,
Context.ElapsedMilliseconds / 1000]));
LResult.Free;
end)
.WithOnError(
procedure(const ErrorMessage: string; const Context:
TThreadContext)
begin
MemoMsg.Lines.Add(Format('[Error] Thread "%s" (ID: %d) failed:
%s', [Context.ThreadName, Context.ThreadID, ErrorMessage]));
end)
.WithOnCancel(
procedure(Context: TThreadContext)
begin
MemoMsg.Lines.Add(Format('[Cancel] Thread "%s" (ID: %d) was
cancelled.', [Context.ThreadName, Context.ThreadID]));
end)
.WithCompleteWithError(True)
{$IFDEF MSWINDOWS}
.WithThreadPriority(PriorityIdle)
{$ENDIF}
.WithThreadName('Download JSON')
.WithThreadId(3);
TSafeThread4D.ExecuteThread(DownloadJSONParams);
end;Trocar a "Thread Anônima" por uma "TThread Tradicional" traria algum benefício real na TSafeThread4D?
Embora a TSafeThread4D utilize internamente uma Thread Anônima para executar seu fluxo, isso não significa que seu uso seja trivial ou equivalente ao uso direto no código de uma simples Thread Anônima. A TSafeThread4D encapsula essa criação dentro de um mecanismo sofisticado, seguro e extensível, promovendo uma arquitetura clara e controlada. O que antes seria um TThread.CreateAnonymousThread(...).Start solto e arriscado, torna-se uma cadeia previsível com WithOnExecute, WithOnSuccess, WithOnCancel, WithOnComplete etc., além de sincronizações automáticas com a UI — tudo de forma fluente e declarativa.
Vantagens em usar "TThread Tradicional (classe derivada) na TSafeThread4D":
- Maior controle no ciclo de vida
Permite sobrescrever métodos comoExecute, DoTerminate, e utilizar WaitFor de maneira mais controlada. - Facilidade de debugging avançado
Threads com nomes definidos em classes derivadas podem ser mais fáceis de rastrear em algumas ferramentas de profiling e debugging. - Reuso e Extensão OO
Uma classeTMySafeThreadpoderia ser reutilizada com herança ou composição, separando responsabilidades.
Desvantagens em usar "TThread Tradicional (classe derivada) na TSafeThread4D":
- Aumento da complexidade sem benefício prático visível
ATSafeThread4Djá oferece toda a estrutura de controle, segmentação e segurança que uma TThread derivada permitiria. A troca seria puramente estrutural, sem ganho de desempenho ou segurança. - A Thread Anônima já está completamente encapsulada
Como não há lógica solta no código do usuário, o uso da thread anônima torna o código daTSafeThread4Dmais limpo e direto — sem expor ao usuário os detalhes do ciclo de vida da thread. - A instância da classe anônima nunca vaza
ComoFreeOnTerminate := Truee não há atribuições externas, não há risco de vazamento ou de ciclo de vida indefinido. - Debugging já é assistido por
NameThreadForDebugging
Mesmo com Thread Anônima, o nome da thread está sendo definido com base noWithThreadName, e oWithThreadIdtambém está sendo injetado para auxiliar.
Android: Sem mais ANRs
No Android, tarefas que bloqueiam a MainThread por alguns segundos disparam a mensagem:
“O aplicativo não está respondendo. Deseja fechá-lo ou aguardar?”
Com TSafeThread4D, isso não acontece mais. A tarefa ocorre fora da UI, a interface continua fluida e os componentes continuam ativos.
Benefícios em Destaque
A TSafeThread4D surge como uma evolução natural das práticas com threads em Delphi, eliminando a repetição de código e os riscos comuns em TThread cru ou TTask. Enquanto TThread.CreateAnonymousThread exige tratamento manual de sincronismos, erros e lockings — sujeitos a erros humanos —, a TSafeThread4D oferece estrutura fluente com fases bem definidas, tratamento de exceções centralizado e cancelamento nativo. Além disso, ela permite lidar facilmente com múltiplas threads, definir prioridades no Windows, e manter uma UI responsiva sem esforço. É uma ferramenta acessível, elegante e eficiente para cenários UI+background típicos em aplicações Delphi FMX.
Considerações Finais
A TSafeThread4D é mais que um wrapper de TThread: é uma arquitetura para operações assíncronas com controle completo, clareza de fluxo e foco em segurança e produtividade. Ideal para quem deseja escrever código responsivo, seguro e bem organizado no Delphi FMX.
Com ela, você não precisa mais escrever o mesmo try..except ouSynchronize toda vez. E pode focar no que realmente importa: a lógica da sua aplicação.
“ATSafeThread4Dé o primeiro passo de uma arquitetura pensada para evoluir com você.”
Em breve, apresentaremos oTTaskChain4D, que permite encadear váriasTSafeThread4Dcomo tarefas sequenciais com o mesmo controle e elegância.
Esta classe é parte do projeto TheCodeNaked, que promove liberdade criativa, padrões elevados e responsabilidade consciente na criação de software.