Install
$ agentstack add skill-delphicleancode-delphi-spec-kit-threading ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Threading & Multi-Threading — Skill
Use this skill when working with threads, asynchronous tasks and parallelism in Delphi projects.
When to Use
- When performing time-consuming operations without blocking the UI (VCL/FMX)
- When implementing parallel data processing
- When creating servers/workers that process concurrent requests
- When synchronizing access to shared resources
- When managing thread pools and work queues
- By implementing graceful thread cancellation
Golden Rule of Threading in Delphi
> NEVER access visual components (VCL/FMX) directly from a secondary thread. > Use TThread.Synchronize or TThread.Queue to update the UI.
Available Approaches
| Approach | When to Use | Complexity | |-----------|-------------|-------------| | TThread | Full control, long-running threads | Average | | TThread.CreateAnonymousThread | Simple, one-shot tasks | Low | | TTask (PPL) | Modern parallelism, lightweight tasks | Low | | TParallel.For (PPL) | Parallel loops in collections | Low | | TFuture (PPL) | Asynchronous result with return value | Low | | TThreadPool | Reusable Thread Pool | Average | | Dedicated thread (inheritance) | Permanent workers, servers, queues | High |
TThread — Classical Approach
Thread with Inheritance (Recommended for Workers)
type
///
/// Worker thread para processamento em background.
/// Demonstra herança de TThread com cancelamento via Terminated.
///
TDataProcessorThread = class(TThread)
private
FItems: TThreadList;
FOnProgress: TProc;
FOnComplete: TProc;
protected
procedure Execute; override;
public
constructor Create(AItems: TThreadList);
property OnProgress: TProc write FOnProgress;
property OnComplete: TProc write FOnComplete;
end;
constructor TDataProcessorThread.Create(AItems: TThreadList);
begin
inherited Create(True); // Criar suspensa
FreeOnTerminate := True; // Auto-libera ao terminar
FItems := AItems;
end;
procedure TDataProcessorThread.Execute;
var
LList: TList;
LTotal, I: Integer;
begin
try
LList := FItems.LockList;
try
LTotal := LList.Count;
finally
FItems.UnlockList;
end;
for I := 0 to LTotal - 1 do
begin
{ Verificar cancelamento em cada iteração }
if Terminated then
Exit;
{ Processar item }
ProcessItem(I);
{ Atualizar UI via Queue (não-bloqueante) }
if Assigned(FOnProgress) then
TThread.Queue(nil,
procedure
begin
FOnProgress(I + 1, LTotal);
end);
end;
{ Notificar conclusão na main thread }
if Assigned(FOnComplete) then
TThread.Queue(nil,
procedure
begin
FOnComplete(not Terminated);
end);
except
on E: Exception do
begin
TThread.Queue(nil,
procedure
begin
raise EThreadException.Create('Erro no processamento: ' + E.Message);
end);
end;
end;
end;
Use of Dedicated Thread
procedure TfrmMain.btnProcessClick(Sender: TObject);
var
LThread: TDataProcessorThread;
begin
LThread := TDataProcessorThread.Create(FSharedItems);
LThread.OnProgress :=
procedure(ACurrent, ATotal: Integer)
begin
pbrProgress.Max := ATotal;
pbrProgress.Position := ACurrent;
lblStatus.Caption := Format('Processando %d de %d...', [ACurrent, ATotal]);
end;
LThread.OnComplete :=
procedure(ASuccess: Boolean)
begin
if ASuccess then
ShowMessage('Concluído com sucesso!')
else
ShowMessage('Processamento cancelado.');
end;
LThread.Start; // Iniciar a thread
end;
procedure TfrmMain.btnCancelClick(Sender: TObject);
begin
{ Solicitar cancelamento gracioso }
if Assigned(FCurrentThread) then
FCurrentThread.Terminate;
end;
CreateAnonymousThread (Simple Tasks)
///
/// Forma mais simples de executar código em background.
/// Ideal para one-shot tasks sem necessidade de controle avançado.
///
procedure TfrmMain.LoadDataAsync;
begin
btnLoad.Enabled := False;
TThread.CreateAnonymousThread(
procedure
var
LData: TStringList;
begin
LData := TStringList.Create;
try
{ Trabalho pesado (thread secundária — OK!) }
LData.LoadFromFile('C:\dados\arquivo_grande.csv');
Sleep(2000); // Simular processamento
{ Atualizar UI (DEVE usar Synchronize ou Queue) }
TThread.Synchronize(nil,
procedure
begin
mmoOutput.Lines.Assign(LData);
btnLoad.Enabled := True;
lblStatus.Caption := Format('Carregados %d registros', [LData.Count]);
end);
finally
LData.Free;
end;
end
).Start;
end;
Synchronize vs Queue
| Method | Behavior | When to Use | |--------|--------------|-------------| | TThread.Synchronize | Blocking — waits for the main thread to process | When you need the UI result | | TThread.Queue | Non-blocking — queue and continue | Progress, logs, visual updates |
{ Synchronize: BLOQUEIA a thread até a main thread processar }
TThread.Synchronize(nil,
procedure
begin
lblStatus.Caption := 'Processando...';
end);
// A thread só continua AQUI after que a main thread executou o código acima
{ Queue: NÃO BLOQUEIA — enfileira e continua imediatamente }
TThread.Queue(nil,
procedure
begin
lblStatus.Caption := 'Processando...';
end);
// A thread continua IMEDIATAMENTE, sem esperar a main thread
> Recommendation: Prefer Queue whenever possible. Use Synchronize only when you need a result from the UI back in the thread.
PPL — Parallel Programming Library (System.Threading)
TTask — Light Tasks
uses
System.Threading;
///
/// TTask é a forma moderna de executar tarefas em background.
/// Gerenciado automaticamente pelo pool de threads do sistema.
///
procedure TfrmMain.ExecuteMultipleTasks;
var
LTask1, LTask2, LTask3: ITask;
begin
LTask1 := TTask.Create(
procedure
begin
{ Tarefa 1: Carregar dados do banco }
LoadCustomers;
end);
LTask2 := TTask.Create(
procedure
begin
{ Tarefa 2: Processar relatório }
GenerateReport;
end);
LTask3 := TTask.Create(
procedure
begin
{ Tarefa 3: Enviar emails }
SendPendingEmails;
end);
{ Iniciar todas as tarefas em paralelo }
LTask1.Start;
LTask2.Start;
LTask3.Start;
{ Aguardar todas completarem (com timeout) }
TTask.WaitForAll([LTask1, LTask2, LTask3], 30000); // 30s timeout
TThread.Queue(nil,
procedure
begin
ShowMessage('Todas as tarefas concluídas!');
end);
end;
TTask.Run — Direct Shortcut
{ Forma mais simples de executar em background via PPL }
TTask.Run(
procedure
begin
{ Código executado no ThreadPool }
PerformHeavyCalculation;
TThread.Queue(nil,
procedure
begin
lblResult.Caption := 'Cálculo concluído';
end);
end);
TParallel.For — Parallel Loops
uses
System.Threading,
System.SyncObjs;
///
/// TParallel.For distribui iterations do loop entre múltiplas threads.
/// Ideal para processamento de coletions independentes.
///
procedure TfrmMain.ProcessImagesParallel;
var
LFiles: TArray;
LProcessed: Integer;
LLock: TCriticalSection;
begin
LFiles := TDirectory.GetFiles('C:\Images', '*.jpg');
LProcessed := 0;
LLock := TCriticalSection.Create;
try
TParallel.For(0, High(LFiles),
procedure(AIndex: Integer)
begin
{ Cada imagem processada em thread separada }
ResizeImage(LFiles[AIndex]);
{ Atualizar contador de forma thread-safe }
LLock.Enter;
try
Inc(LProcessed);
finally
LLock.Leave;
end;
end);
ShowMessage(Format('%d imagens processadas', [LProcessed]));
finally
LLock.Free;
end;
end;
> ⚠️ CAUTION: Each iteration of TParallel.For can run on different threads. Shared variables MUST be protected with TCriticalSection, TMonitor or TInterlocked.
TFuture — Asynchronous Result
uses
System.Threading;
///
/// TFuture executa uma tarefa e returns um valor quando pronto.
/// A leitura de .Value bloqueia até o resultado estar disponível.
///
procedure TfrmMain.CalculateAsync;
var
LFuture: IFuture;
begin
LFuture := TFuture.Create(
function: Double
begin
{ Cálculo pesado em background }
Sleep(3000);
Result := CalculateComplexFormula(FInputData);
end);
LFuture.Start;
{ Fazer outras coisas enquanto o cálculo roda... }
PrepareReport;
{ Pegar o resultado (bloqueia SE ainda não terminou) }
ShowMessage(Format('Resultado: %.2f', [LFuture.Value]));
end;
Thread-Safety — Resource Protection
TCriticalSection
type
TThreadSafeCounter = class
private
FCount: Integer;
FLock: TCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure Increment;
procedure Decrement;
function GetValue: Integer;
end;
constructor TThreadSafeCounter.Create;
begin
inherited;
FLock := TCriticalSection.Create;
FCount := 0;
end;
destructor TThreadSafeCounter.Destroy;
begin
FLock.Free;
inherited;
end;
procedure TThreadSafeCounter.Increment;
begin
FLock.Enter;
try
Inc(FCount);
finally
FLock.Leave; // ALWAYS no finally!
end;
end;
function TThreadSafeCounter.GetValue: Integer;
begin
FLock.Enter;
try
Result := FCount;
finally
FLock.Leave;
end;
end;
TMonitor (Native Object Lock)
{ TMonitor usa o próprio objeto como lock — sem criar TCriticalSection }
procedure TThreadSafeList.AddItem(const AItem: string);
begin
TMonitor.Enter(FList);
try
FList.Add(AItem);
finally
TMonitor.Exit(FList);
end;
end;
TInterlocked (Atomic Operations)
{ Para operações simples em Integer/Int64 — sem lock explícito }
TInterlocked.Increment(FProcessedCount);
TInterlocked.Decrement(FPendingCount);
TInterlocked.Add(FTotalBytes, LBytesRead);
TInterlocked.Exchange(FOldValue, LNewValue);
TInterlocked.CompareExchange(FTarget, LNewVal, LExpectedVal);
TThreadList (Thread-Safe List)
var
FSharedList: TThreadList;
{ Thread A: adicionar }
LList := FSharedList.LockList;
try
LList.Add('item');
finally
FSharedList.UnlockList;
end;
{ Thread B: ler }
LList := FSharedList.LockList;
try
for LItem in LList do
ProcessItem(LItem);
finally
FSharedList.UnlockList;
end;
TMultiReadExclusiveWriteSynchronizer (MREWS)
type
TThreadSafeCache = class
private
FData: TDictionary;
FLock: TMultiReadExclusiveWriteSynchronizer;
public
constructor Create;
destructor Destroy; override;
function TryGet(const AKey: string; out AValue: string): Boolean;
procedure Put(const AKey, AValue: string);
end;
function TThreadSafeCache.TryGet(const AKey: string; out AValue: string): Boolean;
begin
FLock.BeginRead; // Múltiplas threads podem ler simultaneamente
try
Result := FData.TryGetValue(AKey, AValue);
finally
FLock.EndRead;
end;
end;
procedure TThreadSafeCache.Put(const AKey, AValue: string);
begin
FLock.BeginWrite; // Apenas uma thread pode escrever por vez
try
FData.AddOrSetValue(AKey, AValue);
finally
FLock.EndWrite;
end;
end;
Producer-Consumer Pattern
type
///
/// Producer-Consumer com TThreadedQueue (fila thread-safe).
///
TProducerConsumerDemo = class
private
FQueue: TThreadedQueue;
FProducerThread: TThread;
FConsumerThread: TThread;
public
constructor Create;
destructor Destroy; override;
procedure Start;
procedure Stop;
end;
constructor TProducerConsumerDemo.Create;
begin
inherited;
{ QueueDepth=100, PushTimeout=1000ms, PopTimeout=1000ms }
FQueue := TThreadedQueue.Create(100, 1000, 1000);
end;
procedure TProducerConsumerDemo.Start;
begin
{ Producer: gera itens continuamente }
FProducerThread := TThread.CreateAnonymousThread(
procedure
var
I: Integer;
begin
I := 0;
while not TThread.Current.CheckTerminated do
begin
Inc(I);
FQueue.PushItem(Format('Item_%d', [I]));
Sleep(100);
end;
end);
FProducerThread.FreeOnTerminate := False;
{ Consumer: processa itens da fila }
FConsumerThread := TThread.CreateAnonymousThread(
procedure
var
LItem: string;
LResult: TWaitResult;
begin
while not TThread.Current.CheckTerminated do
begin
LResult := FQueue.PopItem(LItem);
if LResult = wrSignaled then
ProcessItem(LItem);
end;
end);
FConsumerThread.FreeOnTerminate := False;
FProducerThread.Start;
FConsumerThread.Start;
end;
procedure TProducerConsumerDemo.Stop;
begin
FProducerThread.Terminate;
FConsumerThread.Terminate;
FProducerThread.WaitFor;
FConsumerThread.WaitFor;
FProducerThread.Free;
FConsumerThread.Free;
end;
Events and Signage
TEvent
uses
System.SyncObjs;
var
FStopEvent: TEvent;
{ Criar }
FStopEvent := TEvent.Create(nil, True, False, ''); // Manual reset, initially unsignaled
{ Thread: esperar sinal }
procedure TWorkerThread.Execute;
begin
while not Terminated do
begin
{ Esperar até 500ms por sinal de parada }
if FStopEvent.WaitFor(500) = wrSignaled then
Break;
{ Fazer trabalho }
DoWork;
end;
end;
{ Main thread: sinalizar parada }
FStopEvent.SetEvent;
TSemaphore (Competition Threshold)
uses
System.SyncObjs;
var
FSemaphore: TSemaphore;
{ Limitar a 5 threads simultâneas }
FSemaphore := TSemaphore.Create(nil, 5, 5, '');
{ Em cada thread: }
FSemaphore.Acquire;
try
{ Apenas 5 threads podem estar aqui simultaneamente }
PerformLimitedWork;
finally
FSemaphore.Release;
end;
Custom Thread Pool
type
TCustomThreadPool = class
private
FThreads: TObjectList;
FQueue: TThreadedQueue;
FMaxThreads: Integer;
public
constructor Create(AMaxThreads: Integer = 4);
destructor Destroy; override;
procedure QueueWork(AProc: TProc);
end;
constructor TCustomThreadPool.Create(AMaxThreads: Integer);
var
I: Integer;
begin
inherited Create;
FMaxThreads := AMaxThreads;
FQueue := TThreadedQueue.Create(1000, INFINITE, 500);
FThreads := TObjectList.Create(True);
for I := 0 to FMaxThreads - 1 do
begin
var LThread := TThread.CreateAnonymousThread(
procedure
var
LProc: TProc;
LResult: TWaitResult;
begin
while not TThread.Current.CheckTerminated do
begin
LResult := FQueue.PopItem(LProc);
if (LResult = wrSignaled) and Assigned(LProc) then
try
LProc();
except
on E: Exception do
{ Log error, don't crash the pool thread }
TThread.Queue(nil,
procedure
begin
// Logger.LogError(E);
end);
end;
end;
end);
LThread.FreeOnTerminate := False;
LThread.Start;
FThreads.Add(LThread);
end;
end;
procedure TCustomThreadPool.QueueWork(AProc: TProc);
begin
FQueue.PushItem(AProc);
end;
Cancellation with Token
type
ICancellationToken = interface
['{CANCEL01-0001-0001-0001-000000000001}']
function IsCancelled: Boolean;
procedure Cancel;
procedure ThrowIfCancelled;
end;
TCancellationToken = class(TInterfacedObject, ICancellationToken)
private
FCancelled: Integer; // 0=false, 1=true (atômico)
public
function IsCancelled: Boolean;
procedure Cancel;
procedure ThrowIfCancelled;
end;
function TCancellationToken.IsCancelled: Boolean;
begin
Result := TInterlocked.Read(FCancelled) = 1;
end;
procedure TCancellationToken.Cancel;
begin
TInterlocked.E
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [delphicleancode](https://github.com/delphicleancode)
- **Source:** [delphicleancode/delphi-spec-kit](https://github.com/delphicleancode/delphi-spec-kit)
- **License:** MIT
- **Homepage:** https://inovefast.com.br
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.