Jump to content
bravesofts

Introducing My Delphi TFuture PPL For Thread Safe UI

Recommended Posts

TFuture-Async.thumb.jpg.c2b9f6bce05457d9003917f2e061c8c2.jpg

Delphi Async Helpers with TFuture<T>:

This demo project provides a clean and safe way to perform background operations in Delphi using TTask and TFuture<T>, with a focus on keeping the UI responsive and preventing memory leaks.

 

📦 What's Inside

  • API.Helpers.pas: A generic async helper unit
  • Main.View.pas: A VCL form demonstrating good and bad usage of futures and background tasks.

 


🔧 Helper Static Class: TSimpleFuture<T>

A simple abstraction over TTask.Future<T> and TTask.Run to make async coding easier.

type
  TConstProc<T> = reference to procedure (const Arg1: T);
  
  TSimpleFuture<T> = class
    class procedure Run(const aQuery: TFunc<T>; const aReply: TConstProc<T>); static;
    class procedure RunFuture(const aQuery: TFunc<T>; const aReply: TConstProc<T>); static;
  end;

 🖼 Demo Form: `Main.View.pas`

 

The form includes buttons that demonstrate the following patterns:

 

✅ Correct Usage (Non-Blocking): 

 

  •   `💪 BtnStartWithHelper`:
TSimpleFuture<string>.RunFuture(function: string begin
    Sleep(2000);
    Result := TimeToStr(Now) + ' ✅ تم التنفيذ';
  end, LogReply);

> ✅ Background-safe 

> ✅ Memory-safe 

> ✅ Beginner-friendly

 

  • ⚠️ Incorrect Usage (`BtnWrongUse`):
var LFuture := TTask.Future<string>(...);
LogReply(LFuture.Value); // ❌ Blocks the main thread!

> ❌ This freezes the UI and defeats the purpose of async programming.

  • ✅ Safe Manual Usage(`BtnWaitOutsideMainThread`):
LFuture := TTask.Future<Integer>(...);

TTask.Run(
  procedure
  begin
    var LValue := LFuture.Value; // Block or wait inside background thread.
    TThread.Queue(nil, procedure begin
      LogReply('Result: ' + LValue.ToString); // update UI on main thread
      LFuture := nil; // release `IFuture<T>` reference to avoid Memory Leak (TCompleteEventWrapper etc of internal thread pool that service the TTask).
    end);
  end);

> ✅ Keeps UI free.  
> ✅ Releases `LFuture` to prevent leaks.

 

  • 🧪 Simulating `IFuture<T>` with `ITask`(`BtnSimulateIFuture`):
var
  LResult: string;
  LFuture := TTask.Run(...);

  TTask.Run(procedure
  begin
    LFuture.Wait; // Call wait inside background thread like IFuture<T>.Value does ..
    TThread.Queue(nil, procedure begin
      LogReply(LResult);
      LFuture := nil; // release `IFuture<T>` reference to avoid Memory Leak (TCompleteEventWrapper etc of internal thread pool that service the TTask).
    end);
end);

> 🧠 A useful trick for simulating `Future.Value` behavior without using `TFuture<T>`.

 

  • 🔁 Future Monitoring Pattern(`BtnMonitorSolution`):

A more advanced way to ensure task completion:

var LFuture := TTask.Future<string>(...);

  TTask.Run(procedure begin
    while not (LFuture.Status in
      [TTaskStatus.Completed, TTaskStatus.Canceled, TTaskStatus.Exception]) do
      TThread.Sleep(100); // Reduce CPU Usage ..(Check every 100 Millisec).

    TThread.Queue(nil, procedure begin
      if LFuture.Status = TTaskStatus.Completed then
        LogReply(LFuture.Value) else
        LogReply('Future Failled or Canceled !!');
        LFuture := nil; // release `IFuture<T>` reference to avoid Memory Leak (TCompleteEventWrapper etc of internal thread pool that service the TTask).
    end);
  end);

🧼 Best Practices (in my opinion now):

✅ Do :
- Use `TThread.Queue` to update UI  
- Use `TFuture.Value` **only from background threads**  
- Set `LFuture := nil` to release memory  

❌ Don’t : 
- Call `.Value` on the main thread.
- Forget to release `IFuture<T>` reference to avoid Memory Leak (TCompleteEventWrapper etc of internal thread pool that service the TTask). 
- Update UI directly from background threads.

 

🧰 Requirements:

- Delphi XE7+  
- VCL application (not mandatory, but my Demo is VCL)  
- `System.Threading` unit.

 

📜 License

Free to use and modify in any personal or commercial project.

 

🧠 Design Philosophy: What Future Really Means  
In general software design, a Future is an abstraction that represents a promise of a result to be available in the future. It is not intended to be synchronously accessed using .Value, especially not from the main thread.  

❌ Misconception: Using .Value Is Asynchronous  
The Future is not designed for synchronous use — instead, it should be part of a fully asynchronous programming style, ideally involving a callback mechanism.  
  
Calling .Value is essentially a blocking call, which defeats the purpose of using Future in the first place.  
  
✅ The Core Idea Behind Future  
The essence of the Future abstraction is:  
  
🔹 A promise for a future result without blocking the current thread, preferably using a callback to handle the result when it’s ready.  

So using .Value is almost equivalent to Task.Wait — not a true asynchronous pattern  

⚠️ Using .Value on the Main Thread Is Misleading!  
One of the most common pitfalls developers face with IFuture<T> in Delphi is the assumption that it is meant to be accessed using .Value.  
  
In reality, this goes against the very design philosophy of Future in most programming languages.  
  
In Delphi, calling .Value internally does something like this: 

function TFuture<T>.GetValue: T;
begin
  Wait; // ⛔ This blocks the current thread!
  Result := FResult;
end;

So, it's not just about when the computation starts — it’s about how you consume the result in a way that doesn't harm the user experience.  
  
  🔄 Summary  
.Value = Blocking = like Wait  
  
Future's goal = Non-blocking promise, best used with callbacks  
  
Using .Value in UI = ❌ Breaks the async model, risks freezing UI  
  
Best practice = Use background thread + TThread.Queue for result delivery  

  
## 🙌 Contributions

Feel free to open an issue or PR if you want to extend this helper for things like:
- Cancellation
- Progress reporting
- `Future.ContinueWith(...)` chaining.


🔧 if you find any Remarks or Suggestions , please Notify me bellow.

 

Thank you for your Time my Friends.

 

My Ready to use Github Repo here.

Share this post


Link to post

The whole point of Delphi Future implementation is to be a blocking call. Now, we can discuss whether this kind of design is useful or not and in which circumstances. 

 

However, your improved Future is mostly useless. Instead of creating a future and then resolving it in additional task (you are involving two threads here) you should just run the code in the task and that is it.

 

Simply use

 

TTask.Run(
  procedure
  begin
    var LValue := (...);
    TThread.Queue(nil, procedure begin
      LogReply('Result: ' + LValue.ToString); // update UI on main thread that service the TTask
    end);
  end);

No need to overcomplicate things.

  • Thanks 1

Share this post


Link to post
2 minutes ago, Dalija Prasnikar said:

The whole point of Delphi Future implementation is to be a blocking call

could you please introduce real work scenario where Future blocking call is usefull.

Share this post


Link to post

Isn't the point of Future that you create the Future and it immediately begins processing something in a background thread? Usually, it is calculating some sort of value that you don't need for a while.

 

Then you execute a bunch of other code. 

 

When then code is done, you request the future.Value. This will block until the thread calculation you started originally is completed (or not block at all if the calculation was already done).

 

If you are not going to block and wait for the value at some point, why use a future? Why not just launch it in a background thread, like Dalija suggested above?

 

For example, you have a large matrix of values, and you need to calculate the mean of all the columns. But you dont need the mean until the very end of the method. So you can start calculating the mean at the beginning of the method, in a future. The rest of the method can execute, and at the very end you request the mean of all the columns from the future and do what you need with it.  Or this can be the result of a complex database query inside the future, or any number of examples. 

 

There is not much difference between a TFuture and launching a TTask at the beginning of the method and then doing TTask.WaitForAllTasks passing in the task you created as far as I can tell. Just some semantic niceties with TFuture if the background task you are running just has a single result.

Edited by Dave Novo

Share this post


Link to post

Generally speaking I prefer offloading background tasks to a threadpool in-queue. The threadpool messages the main thread when the work is done. The main thread then retrieves the response from the threadpool out-queue.(i.e. mailbox principle).

If there are multiple parallell tasks (i.e. a job), the main thread will need to know that, and whenever a thread completes, check if it fullfill the needs of the job and then fire off whatever should happen after the job is complete.

 

There really is no single best way to deal with background processing - except ensuring that the main thread is not blocked.

Share this post


Link to post
1 hour ago, bravesofts said:

could you please introduce real work scenario where Future blocking call is usefull.

It can be useful if you have some code running between the point where you start future and point where you retrieve its value:

 

LFuture := TTask.Future<Integer>(...); // start future task
// ... some other code 
LFuture.Value // use future value  

If you just start future and then immediately ask for its value then future is completely useless:

LFuture := TTask.Future<Integer>(...); // start future task
LFuture.Value // use future value  

 

Share this post


Link to post
23 minutes ago, Dalija Prasnikar said:

It can be useful if you have some code running between the point where you start future and point where you retrieve its value:

but how "my some code" that is between knows exactlly how to continue while future still not ready, inorder to avoid app touching in bad time the 

LFuture.Value // use future value  

there is no callback in tfuture to use unless monitoring her status with while loop which also freeze the app

 

Edited by bravesofts

Share this post


Link to post
5 minutes ago, bravesofts said:

here is no callback in tfuture to use unless monitoring her status with while loop which also freeze the app

 

That is the idea behind future to do something until you definitely need the result of the future. If you want to check if the future is finished you can use the wait method.

Share this post


Link to post
1 minute ago, Lars Fosdal said:

Wait = Block. 

Read the documentation:

 

function Wait(Timeout: Cardinal = INFINITE): Boolean; overload;

 

You do not have to wait for infinite.

Share this post


Link to post

But you are still blocking, if looping around a wait.
Are you going to put a ProcessMessages in that loop?

Share this post


Link to post
26 minutes ago, bravesofts said:

but how "my some code" that is between knows exactlly how to continue while future still not ready, inorder to avoid app touching in bad time the 

You don't and that is the whole purpose of blocking Future, to wait until the result can be used. If you don't want to block, then don't use future at all. Just use task instead.

 

You are trying to solve the problem that does not exists. Task is async, future is just a blocking task. Trying to make PPL future async is pointless.

  • Like 1

Share this post


Link to post

💬 Is Future an accurate name?

From a theoretical computer science and functional programming perspective, yes — the term Future (or Promise) has a well-established meaning:

A Future<T> represents a value that will be available at some point in the future.

But in my critique is fair in the Delphi-specific context, because Delphi’s TFuture<T> is not particularly “intelligent” in how it informs you when the value is ready. Instead, it expects you to pull (.Value) or manually monitor its .Status.

 

Quote

“This so-called Future doesn’t notify me when it’s done !!— I have to poll it or block for it. So what’s so ‘future’ about it?”

Delphi’s TFuture<T> is more like a deferred computation container than a true reactive future like in JavaScript (Promise), Java (CompletableFuture)

 

🧠 Final Thought

If Delphi’s TFuture<T> had a callback or allowed chaining with .OnComplete(...), then the name “Future” would feel more earned.

As it stands:
🔹 It’s a Future — but you must chase it.
🔸 It holds a value — but it won’t tell you when.

 

The Accurate name for it in my opinion is this "HalfPromise" Fits Perfectly.

  • It promises a result... eventually.

  • But it doesn't promise to tell you when it's ready.

  • No OnComplete, no Then, no observer pattern — you're left guessing or blocking.

  • So it's not a full Promise, just... half of one.

what you think friends ?

Share this post


Link to post
10 minutes ago, Lars Fosdal said:

But you are still blocking, if looping around a wait.
Are you going to put a ProcessMessages in that loop?

 

The question was whether it is possible to check if the future is completed or not. Method Wait can be used to achieve that. (The design of wait could be improved to support non-blocking call, but we can argue if a couple of ms delay is significant or not.) 

 

Nobody mentioned that the future is going to be used in the main thread. Whenever ProcessMessages should be used or process paint messages depends on how long the calculation should take. If it is under a minute or the application should be blocked during the life of the method, it should not be used.

 

Share this post


Link to post

I understand the reason behind your decision to implement this, but on the other hand, Delphi Promises was published not long ago, which seems very similar to this project. Does it not contain the same functionality, or does it differ in any way? 

Share this post


Link to post
1 hour ago, bravesofts said:

A Future<T> represents a value that will be available at some point in the future.

And that is still true. This value will be available at some point in the future, when you try accessing the value. If that value is not calculated by then, the call will wait until it is. The future guarantees that you will be able to get the value at that specific point without having to worry about whether it is actually ready or not. If you want to access that value immediately, then you don't need future. If you want to avoid blocking UI, then again you don't need future, just run the code in the task or other kind of background thread.

 

Also you should not compare Delphi with other languages that support async/await and which will not block the UI.

 

If you want a future which works with callbacks you should not use PPL future but something else. I have one variant here https://github.com/dalijap/code-delphi-async/tree/master/Part4/24.2 TValue future

Edited by Dalija Prasnikar
  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×