Skip to content

Modern Delphi Multithreading: Write Clean and Uncomplicated Asynchronous Code

Promised Fulfilled

In 2025, during my talks at Embarcadero Conference, Luso Delphi, and CodeRage, I ended my presentation with a few “mysterious” slides (Slides 17 and 18).

I spoke about the developer’s journey: we moved from the manual complexity of TThread, evolved to the efficiency of the PPL (Parallel Programming Library), but something was still missing.

Those who use PPL intensively know the problem. To orchestrate complex pipelines (download data -> process -> save -> update UI), we end up falling into the dreaded “Pyramid of Doom”: excessive nesting of anonymous methods, repetitive try..except blocks, and manual TThread.Queue calls to avoid freezing the screen.

In that slide, I promised a vision of the future: A fluent interface where asynchronous code could be read like a sentence.

The wait is over. The promise has been fulfilled.

Today, I officially present Dext.Threading.Async — the real implementation of that concept, now available in the Dext Framework.

In my book, “Delphi Multithreading”, I dedicate entire chapters to teaching how to protect your threads, avoid Race Conditions, and ensure that exceptions don’t silently crash your application. This theoretical foundation is non-negotiable.

However, in daily practice, writing all this infrastructure “by hand” for every button in your system is exhausting. Look at the traditional pattern:

// The standard way of using PPL
TTask.Run(procedure
begin
try
var Data := DownloadBytes;
// Nesting...
TThread.Synchronize(nil, procedure
begin
UpdateUI(Data); // What if the Form is already closed? Access Violation.
end);
except
on E: Exception do HandleError(E); // Repetitive boilerplate
end;
end);

Comparative Concept

With the new Dext engine, we abstract the complexity of ITask, IFuture, and exception handling into a declarative syntax.

Let’s look at a real example. Imagine a Dashboard that needs to load 3 heavy panels simultaneously when opening the screen. If we did this sequentially, the user would wait for the sum of the times. With Dext, we fire them in parallel and the code remains clean:

uses Dext.Threading.Async;
procedure TFormDashboard.LoadDashboard;
begin
// 1. Load User Profile (I/O Bound)
// Fire and forget complexity. Dext manages the thread.
var Task1 := TAsyncTask.Run<TUserProfile>(
function: TUserProfile
begin
// Executes in Background Thread Pool
Result := UserService.GetProfile(Self.UserId);
end)
.ThenBy(procedure(User: TUserProfile)
begin
// Executes additional background processing if necessary
LogAccess(User);
end)
.OnComplete(procedure(User: TUserProfile)
begin
// AUTOMATICALLY executes on the Main Thread (safe for VCL/FMX)
UserLabel.Caption := 'Welcome, ' + User.Name;
imgAvatar.Bitmap := User.Avatar;
end)
.OnException(procedure(E: Exception)
begin
// Centralized error handling on Main Thread
ShowMessage('Error loading profile: ' + E.Message);
end)
.Start; // Starts execution
// 2. Load Financial Chart (CPU Bound)
// Executes in PARALLEL to the previous one
var Task2 := TAsyncTask.Run<TFinancialData>(
function: TFinancialData
begin
// Automatically checks for cancellation before starting
Result := FinanceService.CalculateHeavyMetrics(CurrentYear);
end)
.OnComplete(procedure(Data: TFinancialData)
begin
ChartSales.Series[0].Add(Data.Values);
end)
.Start;
// 3. Update Notifications
var Task3 := TAsyncTask.Run<Integer>(
function: Integer
begin
Result := NotificationService.CountUnread;
end)
.OnComplete(procedure(Count: Integer)
begin
Badge.Text := Count.ToString;
Badge.Visible := Count > 0;
end)
.Start;
// Wait for all tasks to complete
TAsyncTask.WaitForAll([Task1, Task2, Task3]);
end;

What do we gain here?

  1. Readability: We read the code from top to bottom, like a story.
  2. Thread Safety: OnComplete and OnException guarantee synchronization with the Main Thread or calling thread. No more TThread.Queue scattered throughout the code.
  3. Exception Propagation: If GetProfile fails, the flow skips ThenBy and goes straight to OnException. No try..except polluting the business logic.

But what if the user closes the form while these 3 tasks are running? Will we have an Access Violation when trying to update the UserLabel?

This is where the theory I teach in Chapter 4 of the book comes in: Cooperative Cancellation.

Dext implements the ICancellationToken pattern (inspired by .NET) seamlessly. You can pass a token to the task, and the fluent chain checks that token before each step.

// Binds the Token to the Form's lifecycle
FTokenSource := TCancellationTokenSource.Create;
TAsyncTask.Run(...)
.WithCancellation(FTokenSource.Token) // The magic happens here
.OnComplete(...)
.Start;
procedure TFormDashboard.FormDestroy(Sender: TObject);
begin
// When destroying the form, cancel everything pending.
// Dext prevents OnComplete (which accesses visual components) from executing.
FTokenSource.Cancel;
end;

This solves one of the most common and hard-to-track bugs in Delphi Multithread applications: the callback trying to access a destroyed object.

The Dext Framework delivers the tool ready for production. But to use multithreading masterfully, you need to understand what is really happening. Why is the Thread Pool better than creating manual Threads? What is Context Switching? How do Synchronization Primitives work?

Delphi Multithreading Book

That’s why I say the Book and the Framework make a perfect pair:

I fulfilled my promise made at CodeRage. Fluent Tasks is no longer just a PowerPoint slide; it’s real code you can use today to eliminate complexity from your project.

Delphi Multithreading Book 3D

I invite you to download Dext, test Dext.Threading.Async, and if you want to master the engineering behind it, secure your copy of the book on my site.

Async API Documentation: https://github.com/cesarliws/dext/blob/main/Docs/async-api.md

Download Dext: https://github.com/cesarliws/dext

Get the Book: https://www.cesarromero.com.br/

Let’s code (in parallel)! 🚀

#Delphi #Multithreading #DelphiMultithreadingBook #DextFramework #AsyncProgramming #CodeRage #SoftwareArchitecture #OpenSource