TheCodeNaked

Delphi — Paralelismo: Como Dominar Threads no Delphi com TSafeThread4D (Copy)

Apesar das CPUs disponibilizarem múltiplos núcleos, muitos desenvolvedores ainda não se beneficiam dessa capacidade por desconhecimento do paralelismo — deixando de lado um dos recursos mais poderosos do hardware atual. O uso de múltiplas threads é o caminho para aproveitar esse poder.


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.Queue ou TThread.Synchronize.

WithOnInitialize

Executada na Main Thread, no início da execução.

  • Ideal para preparar a interface.
  • Pode desabilitar botões, iniciar TAniIndicator, limpar TMemo, 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 Threadapenas 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 OnTerminate do objeto thread interno.

WithOnError

Chamado quando ocorre qualquer exceção dentro da thread.

  • Recebe o ErrorMessage e o TThreadContext.
  • 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:
    IdleLowestLowerNormalHigherHighestTimeCritical.

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 da TSafeThread4D, incluindo o uso do TAniIndicatorTProgressBar e 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 WithOnExecuteWithOnSuccessWithOnCancelWithOnComplete 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":

  1. Maior controle no ciclo de vida
    Permite sobrescrever métodos como Execute, DoTerminate, e utilizar WaitFor de maneira mais controlada.
  2. 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.
  3. Reuso e Extensão OO
    Uma classe TMySafeThread poderia ser reutilizada com herança ou composição, separando responsabilidades.

Desvantagens em usar "TThread Tradicional (classe derivada) na TSafeThread4D":

  1. Aumento da complexidade sem benefício prático visível
    TSafeThread4D já 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.
  2. 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 da TSafeThread4D mais limpo e direto — sem expor ao usuário os detalhes do ciclo de vida da thread.
  3. A instância da classe anônima nunca vaza
    Como FreeOnTerminate := True e não há atribuições externas, não há risco de vazamento ou de ciclo de vida indefinido.
  4. Debugging já é assistido por NameThreadForDebugging
    Mesmo com Thread Anônima, o nome da thread está sendo definido com base no WithThreadName, e o WithThreadId també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

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

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.

“A TSafeThread4D é o primeiro passo de uma arquitetura pensada para evoluir com você.”

Em breve, apresentaremos o TTaskChain4D, que permite encadear várias TSafeThread4D como 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.
Sobre o autor

TheCodeNaked

No TheCodeNaked, programar é consequência, não ponto de partida. Antes do código, vem a dúvida, a análise, o contexto. Não seguimos fórmulas — questionamos. Criar software é pensar com clareza. O resto é só digitação.

TheCodeNaked

Criar com clareza. Codificar com intenção.

TheCodeNaked

Ótimo! Você se inscreveu com sucesso.

Bem-vindo de volta! Você acessou com sucesso.

Você se inscreveu com sucesso o TheCodeNaked.

Sucesso! Verifique seu e-mail para acessar com o link mágico.

As suas informações de faturamento foram atualizadas.

Seu pagamento não foi atualizado