Processamento Assíncrono, Threads e Lotes no Delphi com FireDAC
Como carregar milhões de registros sem travar a interface
Em aplicações que trabalham com grandes volumes de dados — especialmente sistemas corporativos, ERPs ou softwares industriais — é comum enfrentar travamentos na interface ao executar consultas pesadas. Isso acontece porque a operação é realizada na main thread, que também é responsável por redesenhar a UI.
Neste artigo, apresento uma abordagem profissional para:
- Processar grandes volumes de dados em threads separadas
- Ler registros em lotes (paginações)
- Atualizar a interface de forma segura com
TThread.Queue - Acumular todos os dados numa memória temporária
- Atualizar a memória da UI apenas no final, sem erros como:
“Cannot perform this operation on a closed dataset”
🧵 Entendendo o conceito: Threads e a Main Thread
O Delphi VCL (e UniGUI) tem uma característica importante:
Somente a main thread pode atualizar a interface.
Qualquer tentativa de mexer em componentes visuais dentro de uma background thread resulta em erros, travamentos, access violation etc.
Por isso usamos:
✔️ TTask.Run
Cria uma operação assíncrona executada em outra thread.
✔️ TThread.Queue
Usado para entregar resultados para a interface de forma segura.
Assim mantemos a UI:
- leve
- responsiva
- sem travamentos
- sem deadlocks
📦 Carregamento em Lotes (Pagination / Chunking)
Quando trabalhamos com bases Firebird grandes, é ruim fazer:
SELECT * FROM tabela
pois a consulta retorna milhares/milhões de registros.
A solução é quebrar a consulta em blocos:
ROWS 1 TO 1000
ROWS 1001 TO 2000
ROWS 2001 TO 3000
...
Isso reduz consumo de memória e mantém desempenho estável.
⚠ Gostaria de acrescentar que também da pra fazer com outros banco de dados.
🧠 TFDMemTable como armazenamento intermediário
Usamos dois TFDMemTable:
1️⃣ TempMem
Armazena os dados dentro da thread background.
2️⃣ TempMemClone
Cópia segura, entregue à UI por TThread.Queue.
3️⃣ FDMem
É a tabela que está conectada à DBGrid / TUniDBGrid.
🚫 Por que não posso passar TempMem direto para a UI?
Porque o TempMem pertence à thread secundária.
Se tentar acessar na UI, ocorre:
"Dataset not in edit mode""Cannot perform this operation on a closed dataset""Operation not allowed in multi-thread environment"- Travamento aleatório em produção
Por isso criamos um clone antes de enviar à UI.
🧩 A Solução Completa
Aqui está o algoritmo final:
- Monta a estrutura do
FDMem - Inicia uma thread (
TTask.Run) - Conta total de registros
- Lê os dados em lotes
- Armazena tudo em
TempMem - No final, faz um clone seguro
- Atualiza o dataset da UI dentro de
TThread.Queue
Sem travamentos e sem inconsistências.
🛠️ Código Final
procedure TfrmPerformanceDemo.PaginacaoECopiaAsyncQueue(
FDQuery: TFDQuery; FDMem: TFDMemTable; Lote: Integer);
var
SQLBase: string;
TempQuery: TFDQuery;
begin
if not Assigned(FDQuery) or not Assigned(FDMem) then Exit;
if Lote <= 0 then Lote := 1000;
SQLBase := FDQuery.SQL.Text;
Self.Tag := 0;
Log2(‘Início do Processo COPY QUEUE…’);
Log(Format(‘Iniciando cópia em segundo plano (lotes de %d registros)…’, [Lote]));
// ⚙️ Cria estrutura do dataset de destino (FDMem)
TempQuery := TFDQuery.Create(nil);
try
TempQuery.Connection := FDQuery.Connection;
TempQuery.SQL.Text := SQLBase + ‘ ROWS 1 TO 1’;
TempQuery.Open;
FDMem.Close;
FDMem.FieldDefs.Clear;
for var i := 0 to TempQuery.FieldCount – 1 do
FDMem.FieldDefs.Add(
TempQuery.Fields[i].FieldName,
TempQuery.Fields[i].DataType,
TempQuery.Fields[i].Size
);
FDMem.CreateDataSet;
finally
TempQuery.Free;
end;
// 🚀 Processo paralelo
TTask.Run(
procedure
var
TotalRegistros, Processados, FimLote: Integer;
TempQuery: TFDQuery;
TempMem: TFDMemTable;
TempMemClone: TFDMemTable;
begin
TempQuery := TFDQuery.Create(nil);
TempMem := TFDMemTable.Create(nil);
try
TempQuery.Connection := FDQuery.Connection;
// 🔢 Conta total de registros
TempQuery.SQL.Text := ‘SELECT COUNT(*) AS TOTAL FROM (‘ + SQLBase + ‘)’;
TempQuery.Open;
TempQuery.FetchOptions.RowsetSize := Lote;
TempQuery.FetchOptions.Mode := fmOnDemand;
TotalRegistros := TempQuery.FieldByName(‘TOTAL’).AsInteger;
TempQuery.Close;
// Cria estrutura do temporário
TempMem.FieldDefs.Assign(FDMem.FieldDefs);
TempMem.CreateDataSet;
Processados := 0;
// 🔁 Lê em lotes
while (Processados < TotalRegistros) and (Self.Tag = 0) do
begin
FimLote := Min(Processados + Lote, TotalRegistros);
TempQuery.SQL.Text := SQLBase + Format(‘ ROWS %d TO %d’, [Processados + 1, FimLote]);
TempQuery.Open;
TempQuery.FetchOptions.RowsetSize := Lote;
TempQuery.FetchOptions.Mode := fmOnDemand;
TempMem.AppendData(TempQuery, True);
Inc(Processados, TempQuery.RecordCount);
Log(Format(‘Processados %d de %d registros…’, [Processados, TotalRegistros]));
TempQuery.Close;
end;
// 🔔 Clona TempMem antes de entrar na thread da UI
TempMemClone := TFDMemTable.Create(nil);
TempMemClone.FieldDefs.Assign(TempMem.FieldDefs);
TempMemClone.CreateDataSet;
TempMemClone.CopyDataSet(TempMem, [coAppend]);
// ✅ Atualiza UI com todos os dados no final
TThread.Queue(nil,
procedure
begin
try
FDMem.DisableControls;
try
if FDMem.Active then
FDMem.Close;
FDMem.CopyDataSet(TempMemClone, [coAppend]);
FDMem.Open;
finally
FDMem.EnableControls;
end;
if Self.Tag = 1 then
Log(‘Cópia cancelada pelo usuário.’)
else
Log(Format(‘Cópia concluída! Total final: %d registros.’, [FDMem.RecordCount]));
Log2(‘Término do Processo COPY QUEUE…’);
finally
TempMemClone.Free;
end;
end
);
finally
TempMem.Free;
TempQuery.Free;
end;
end
);
end;
📌 Benefícios da abordagem
- UI nunca trava
- O usuário continua usando o sistema normalmente
- Performance muito maior
- Garante segurança com FireDAC
- Evita erros de dataset
- Permite cancelar processamento via
Form.Tag - Funciona no VCL e no UniGUI
🎯 Conclusão
Carregar grandes volumes de dados exige uma arquitetura bem planejada. Usando:
TTask.RunTThread.Queue- carregamento em lotes (pagination)
- TFDMemTable com clone seguro
…você obtém uma solução extremamente estável, veloz e profissional — ideal para sistemas de laboratório, ERPs ou portais de consulta de dados.
Grupo Geral no WhatsApp
Participe do Delphi Masters e conecte-se com desenvolvedores de todo o Brasil!
Discussões, dúvidas, dicas e muita troca de conhecimento. Entre agora: https://chat.whatsapp.com/HPwXGINRiDS65VgRDLS2lD
Canal do YouTube Delphi Masters
Conteúdo técnico, lives, tutoriais e entrevistas com feras do Delphi!
Se inscreva e ative o sininho para não perder nada. Acesse: https://www.youtube.com/@delphimasters
Comunidade no DISCORD
Bate-papo em tempo real com a galera do Delphi!
Canais organizados por temas, suporte e muito networking. Entre no Discord: https://discord.gg/pq2YvPZ7Z2
Edição/Revisão:
Diógenes Henrique – Autor
Delphi Masters