Introdução: O Fantasma do “Não Está Respondendo”
Durante décadas, a classe TThread foi o martelo para todos os pregos de concorrência no Delphi. Embora poderosa, ela carrega um fardo pesado: boilerplate excessivo, gerenciamento manual do ciclo de vida e a complexidade de sincronização.
Muitos desenvolvedores ainda escrevem classes herdadas de TThread.Create para tarefas simples. O resultado? Código verboso e difícil de manter. Com a introdução da Parallel Programming Library (PPL), a Embarcadero nos entregou uma abstração superior: o conceito de Tasks (Tarefas).
Este artigo demonstra como migrar do modelo mental de “Threads” para “Tasks”, utilizando TTask e IFuture para executar consultas pesadas simultaneamente, reduzindo drasticamente o tempo de espera do usuário.
O Cenário: O Dashboard Lento
Imagine um cenário comum: Um Dashboard financeiro que precisa carregar três indicadores críticos ao abrir:
- Total de Vendas do Mês (Tempo estimado: 2s)
- Saldo em Caixa (Tempo estimado: 2s)
- Ticket Médio por Região (Tempo estimado: 2s)
A Abordagem Serial (Bloqueante)
No modelo tradicional (executado na Main Thread), o código seria executado sequencialmente:

Durante esses 6 segundos, a aplicação fica “congelada” (o famoso ghosting do Windows), e o usuário não pode clicar em nada.
A Abordagem Paralela (PPL)
Ao disparar as consultas simultaneamente, o tempo total é determinado pela consulta mais lenta, não pela soma delas:

Isso representa uma redução de ~66% no tempo de espera.
A Arquitetura da Solução
Para implementar isso, utilizaremos dois componentes chave da unidade System.Threading:
- IFuture<T>: Uma especialização de
TTaskque promete retornar um valor de um tipo específico (T) no futuro. É ideal para consultas ao banco, pois precisamos do Result (o Dataset ou valor calculado). - TTask.WaitForAll: Um método estático que suspende a execução (de forma controlada) até que um array de tarefas esteja concluído.
Pré-requisito Crítico: Conexão com Banco de Dados
Aviso Importante: Componentes de conexão (como
TFDConnection) não são thread-safe. Você não pode compartilhar a mesma instância de conexão entre threads simultâneas.Solução: Cada Task deve criar sua própria conexão ou, preferencialmente, solicitar uma conexão de um Connection Pool (ex:
TFDManagerno FireDAC) para evitar o overhead de handshake TCP a cada execução.
Implementação Prática
Vamos construir o código. Suponha que temos uma função GetQueryValue que simula a latência do banco.
1. Definindo as Tarefas com IFuture
Em vez de criar classes, usamos Closures (funções anônimas).
uses
System.Threading, System.SysUtils, System.Classes, Vcl.Dialogs;
procedure TFormDashboard.CarregarDashboard;
var
// Definimos Futures que retornarão Double (valores financeiros)
FutVendas: IFuture<Double>;
FutCaixa: IFuture<Double>;
FutTicket: IFuture<Double>;
TotalVendas, SaldoCaixa, TicketMedio: Double;
Stopwatch: TStopwatch;
begin
Stopwatch := TStopwatch.StartNew;
// Indicador visual de carregamento
LayoutLoading.Visible := True;
// 1. Disparar Consulta de Vendas
FutVendas := TTask.Future<Double>(function: Double
begin
// Lembre-se: Crie/Adquira uma nova conexão DB aqui dentro!
Sleep(2000); // Simulando query pesada
Result := 150000.50;
end);
// 2. Disparar Consulta de Caixa
FutCaixa := TTask.Future<Double>(function: Double
begin
Sleep(2000);
Result := 45000.00;
end);
// 3. Disparar Consulta de Ticket Médio
FutTicket := TTask.Future<Double>(function: Double
begin
Sleep(2000);
Result := 125.90;
end);
// O código flui imediatamente até aqui, as tasks já estão rodando em background.
// 4. Thread de Orquestração (Para não travar a VCL enquanto espera)
TTask.Run(procedure
begin
try
// Espera todas as 3 tarefas terminarem.
// Se uma demorar 5s e as outras 2s, ele espera 5s.
TTask.WaitForAll([FutVendas, FutCaixa, FutTicket]);
// Captura os valores (acessar .Value bloqueia se não estiver pronto,
// mas o WaitForAll já garantiu isso).
TotalVendas := FutVendas.Value;
SaldoCaixa := FutCaixa.Value;
TicketMedio := FutTicket.Value;
// 5. Atualizar a VCL (Sincronização Obrigatória)
TThread.Queue(nil, procedure
begin
lblVendas.Caption := FormatFloat('R$ #,##0.00', TotalVendas);
lblCaixa.Caption := FormatFloat('R$ #,##0.00', SaldoCaixa);
lblTicket.Caption := FormatFloat('R$ #,##0.00', TicketMedio);
LayoutLoading.Visible := False;
ShowMessage('Dados carregados em: ' + Stopwatch.Elapsed.ToString);
end);
except
on E: Exception do
begin
TThread.Queue(nil, procedure
begin
ShowMessage('Erro ao carregar dados: ' + E.Message);
LayoutLoading.Visible := False;
end);
end;
end;
end);
end;
Análise Técnica Detalhada
Por que IFuture e não apenas TTask?
Embora o TTask execute código em paralelo, ele é “Fire-and-Forget” (Dispare e Esqueça). O IFuture<T> herda de TTask, mas adiciona a capacidade de transportar o resultado da thread secundária de volta para quem a chamou. Sem isso, você teria que criar variáveis globais ou campos na classe e usar Critical Sections para escrever nelas, aumentando a complexidade.
Tratamento de Exceções
Uma das grandes vantagens da PPL sobre TThread tradicional é o tratamento de exceções.
Se ocorrer um erro dentro da função anônima do FutVendas, a exceção é capturada, armazenada e relançada no momento em que você acessa FutVendas.Value.
No exemplo acima, o try..except envolvendo a captura dos valores lidará corretamente com erros de banco de dados ocorridos nas threads secundárias.
O Papel do ThreadPool
Ao contrário do TThread.Create (que cria uma thread de SO cara para cada instância), a PPL usa um Thread Pool. Ela reutiliza threads existentes. Se você disparar 100 tarefas, a PPL não criará 100 threads; ela usará um número otimizado baseado nos núcleos da sua CPU (ex: 4 a 8 threads) e enfileirará o restante. Isso evita que o sistema operacional entre em colapso por Context Switching.
Resumo das Vantagens
| Característica | TThread Tradicional | PPL (TTask/IFuture) |
| Sintaxe | Verbosa (Classes herdadas) | Limpa (Closures/Anon Methods) |
| Retorno de Dados | Complexo (Campos/Events) | Nativo (IFuture.Value) |
| Gerenciamento | Manual (FreeOnTerminate) | Automático (Interface RefCounting) |
| Recursos | 1 Thread por instância | Thread Pool Otimizado |
| Sincronização | Synchronize explícito | WaitForAll / Queue facilitados |
Conclusão
Ao utilizar TTask e IFuture, transformamos um processo linear e lento em uma operação paralela eficiente. Para o usuário final, a percepção de performance triplicou. Para o desenvolvedor, o código permaneceu linear, legível e seguro, sem a “spaghetti logic” de eventos e mensagens de threads antigas.
Use a PPL para modernizar suas aplicações VCL hoje mesmo. O processador do seu cliente tem múltiplos núcleos; pare de usar apenas um.
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.