Renderizar uma página PDF de alta resolução bloqueia a thread da UI durante centenas de milissegundos. Para um visualizador onde o utilizador navega rapidamente entre páginas, esse bloqueio produz uma interface que não responde — o scroll pára, os botões deixam de responder e a janela pode ficar em branco. A solução é mover a renderização para uma thread de segundo plano e cancelar o trabalho em curso quando o utilizador muda para uma página diferente antes de a renderização atual terminar. O TPdfFuture<T> e o TPdfCancellationTokenSource no PDFiumPas fornecem essa infraestrutura
A abstração de futuro
Um TPdfFuture<T> representa um valor que ainda não está disponível. Embrulha trabalho que corre numa thread de segundo plano e expõe o resultado quando está concluído. O parâmetro de tipo T é o tipo do valor produzido — tipicamente um TBitmap ou uma estrutura de bitmap personalizada para renderização de página PDF
O futuro é criado passando um método anónimo que faz o trabalho de renderização e um token de cancelamento que a thread de segundo plano verifica periodicamente. Quando o token é cancelado — porque o utilizador navegou para uma página diferente — a thread de renderização termina cedo e o resultado é descartado
var
CTS: TPdfCancellationTokenSource;
Future: TPdfFuture<TBitmap>;
begin
CTS := TPdfCancellationTokenSource.Create;
Future := TPdfFuture<TBitmap>.Create(
function(Token: TPdfCancellationToken): TBitmap
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
Bmp.SetSize(PageWidth, PageHeight);
// render into Bmp; check Token.IsCancelled periodically
PdfPage.RenderToBitmap(Bmp, Token);
if Token.IsCancelled then
FreeAndNil(Bmp);
Result := Bmp;
end,
CTS.Token
);
// Store Future and CTS; display result when Future.IsComplete
end;
Cancelar ao mudar de página
O padrão típico de visualizador é: quando o utilizador solicita a página N+1, cancelar o futuro de renderização existente para a página N antes de criar um novo futuro para N+1. O método Cancel em TPdfCancellationTokenSource define o token como cancelado. A thread de renderização que verifica Token.IsCancelled sairá no próximo ponto de verificação, e o futuro sinalizará conclusão com um resultado nulo
procedure TViewer.NavigateToPage(PageIndex: Integer);
begin
// Cancel any in-progress render
if Assigned(FCurrentCTS) then
begin
FCurrentCTS.Cancel;
FCurrentCTS.Free;
end;
FCurrentCTS := TPdfCancellationTokenSource.Create;
FCurrentFuture := TPdfFuture<TBitmap>.Create(
function(Token: TPdfCancellationToken): TBitmap
begin
Result := RenderPage(PageIndex, Token);
end,
FCurrentCTS.Token
);
FCurrentFuture.OnComplete :=
procedure(Bmp: TBitmap)
begin
if Assigned(Bmp) then
DisplayBitmap(Bmp);
end;
end;
Nenhuma thread de renderização anterior continua a correr depois de Cancel ser chamado — a thread verifica o token, vê que está cancelado e sai. Isso evita que renderizações de páginas anteriores consumam recursos de CPU para trabalho cujo resultado será descartado
A correção Synchronize vs Queue na v1.61.0
A v1.61.0 corrigiu uma condição de corrida num caminho de retorno de chamada de conclusão que afetava threads com FreeOnTerminate := True. A questão era a distinção entre Synchronize e Queue na VCL do Delphi
TThread.Queue agenda um método para ser executado na thread principal mais tarde, de forma assíncrona — a thread de segundo plano posta a chamada e continua imediatamente. TThread.Synchronize bloqueia a thread de segundo plano até que a thread principal execute o método. Quando FreeOnTerminate está ativo, a thread liberta-se a si própria assim que o seu método Execute retorna. Se o retorno de chamada de conclusão foi publicado via Queue, a thread pode ter-se libertado antes de a thread principal executar o retorno de chamada, produzindo um acesso a memória após libertação
A correção muda para Synchronize para o retorno de chamada de conclusão quando FreeOnTerminate está ativo. A thread de segundo plano bloqueia até que a thread principal confirme a receção do resultado, tornando seguro que a thread se liberte depois. O custo é que a thread de segundo plano espera brevemente, mas a correção de segurança supera em muito esse custo
// v1.61.0 fix: use Synchronize not Queue when FreeOnTerminate is True
// so the thread does not free itself before OnComplete fires on the main thread.
// No code change needed on the caller side — the fix is inside TPdfFuture<T>.
Ponteiros de método versus métodos anónimos (compatibilidade FPC)
O Delphi suporta métodos anónimos com captura de variáveis de fecho, que é a forma natural de passar contexto de renderização para o futuro. O FPC (Free Pascal Compiler) para Lazarus tem suporte de método anónimo mais limitado nas versões anteriores ao 3.2. O PDFiumPas expõe sobrecargas de TPdfFuture<T> que aceitam ponteiros de método como alternativa, tornando o padrão de futuro utilizável de Lazarus sem exigir suporte de fecho
// Delphi: anonymous method with closure capture
Future := TPdfFuture<TBitmap>.Create(
function(Token: TPdfCancellationToken): TBitmap
begin
Result := RenderPage(FPageIndex, Token); // FPageIndex captured from closure
end,
CTS.Token
);
// Lazarus / FPC: method pointer overload
Future := TPdfFuture<TBitmap>.Create(Self, @Self.DoRenderPage, CTS.Token);
Ambas as formas são funcionalmente equivalentes; a distinção é sintática para compatibilidade com compiladores
O cancelamento progressivo — onde uma página parcialmente renderizada pode ser exibida antes que a renderização completa termine — é abordado em renderização progressiva cancelável. A medição de texto e quebra de linha, que complementa a renderização visual, está em medição de texto PDF para composição. Ambas as funcionalidades fazem parte do Componente PDFiumPas para Delphi e Lazarus