novembro 27, 2025

Processamento Assíncrono, Threads e Lotes no Delphi com FireDAC

Por Diógenes Henrique

 

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:

  1. Monta a estrutura do FDMem
  2. Inicia uma thread (TTask.Run)
  3. Conta total de registros
  4. Lê os dados em lotes
  5. Armazena tudo em TempMem
  6. No final, faz um clone seguro
  7. 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.Run
  • TThread.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