Choosing Between TTask and Parallel.For for Responsive VCL Applications
Guide to picking TTask or Parallel.For in Delphi VCL apps, with constraints, trade‑offs table, and ready‑to‑run code examples.
25 May 2026, 00:53 UTC

When building a VCL application, long‑running work must be moved off the main UI thread to avoid freezing the interface. Delphi provides two high‑level concurrency primitives from the Parallel Programming Library: TTask (available since XE2) and Parallel.For (introduced in 10.4). The decision hinges on the nature of the work and the need to keep the UI responsive.
Decision constraints
- UI thread must remain free to process messages.
- VCL components are not thread‑safe; any UI update must be marshaled back to the main thread.
- Background work should avoid unnecessary thread creation overhead.
Comparison of options
| Aspect | TTask | Parallel.For |
|---|---|---|
| Typical use case | Asynchronous I/O, callbacks, discrete units of work. | Data‑parallel loops where the same operation is applied to many elements. |
| Granularity | Coarse – one task per operation. | Fine – many iterations split across cores. |
| Blocking behaviour | Non‑blocking when started with TTask.Run; you can attach continuations. |
Blocking – the call returns only after all iterations finish. |
| Overhead | Higher per‑unit due to task object creation. | Low – optimized loop partitioning. |
| Thread‑safety requirement | User code must marshal VCL access with TThread.Queue or ForceQueue. |
Loop body must be thread‑safe; avoid shared mutable state. |
When to choose TTask
Use TTask when the work is naturally asynchronous (e.g., waiting for a file, a network response, or a timer) and you want to continue execution without blocking the UI. Because it draws from the thread pool, you avoid the cost of manually creating and destroying TThread instances.
Example: Background task that updates a label
uses System.Threading, System.Threading.Tasks;
procedure TForm1.btnStartClick(Sender: TObject);
begin
Label1.Caption := 'Working…';
TTask.Run(procedure
begin
// Simulate work – replace with real I/O or computation
Sleep(1500);
// Marshal UI update to the main thread
TThread.Queue(nil,
procedure
begin
Label1.Caption := 'Finished';
end);
end);
end;
To verify, run the application, click the button, and observe that the label changes after the pause without the UI freezing. Open the debugger’s Threads window; you will see a worker thread executing the task while the UI update occurs on the main thread.
When to choose Parallel.For
Use Parallel.For for CPU‑bound numeric processing where the same calculation is applied to each element of an array or collection. The call blocks until all iterations finish, so if you need the UI to stay responsive you must run the loop inside a TTask.
Example: Data‑parallel loop inside a task
uses System.Threading, System.Threading.Tasks, Math;
procedure TForm1.btnProcessClick(Sender: TObject);
var
Data: array[0..9999] of Double;
i: Integer;
begin
// Initialise sample data
for i := 0 to High(Data) do
Data[i] := i;
Label1.Caption := 'Processing…';
TTask.Run(procedure
begin
Parallel.For(0, High(Data),
procedure(I: Integer)
begin
// Thread‑safe: each iteration works on its own index
Data[I] := Sqrt(Data[I]) * 1.5;
end);
// Marshal result back to UI
TThread.Queue(nil,
procedure
begin
Label1.Caption := 'Done'; // In a real app you might display a summary
end);
end);
end;
Verification steps: after clicking the button, the UI remains responsive (you can move the window or click other controls). The label changes to “Done” once the loop finishes. In the debugger’s Threads window you will see multiple worker threads executing the loop iterations, while the final UI update runs on the main thread.
Practical tips
- Set the thread‑pool size explicitly if you need to control oversubscription:
TTaskPool.Default.SetMaxWorkerThreads(Environment.ProcessorCount); - When using
Parallel.For, avoid writing to shared variables; use thread‑local accumulators or combine results after the loop. - Always marshal VCL access with
TThread.Queue(orForceQueueif you need the call to execute before returning).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.