Search the Community
Showing results for tags 'ppl'.
Found 3 results
-
promise Introducing My Delphi TFuture PPL For Thread Safe UI
bravesofts posted a topic in I made this
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. -
Threads in PPL thread pool not returning to idle as expected
GeMeMe posted a topic in RTL and Delphi Object Pascal
Using Delphi 10.3.2 I've encountered the following problem (specifically on Windows, not sure about other platforms): After using TParallel or TTask, not all threads in the thread pool return to idle as expected. I've prepared the following simple example: procedure TForm.Button1Click(Sender: TObject); var SW:TStopWatch; procedure Load; begin TParallel.For(0, 99999999, procedure(i: Integer) var T:Single; begin T:=Sin(i/PI); end); end; begin Memo1.Lines.Add('PPL Test ---------------'); Memo1.Lines.Add('Before: '+TThreadPoolStats.Current.Formatted); SW:=TStopWatch.StartNew; Load; Memo1.Lines.Add('Finished in '+SW.Elapsed.ToString); Memo1.Lines.Add('After: '+TThreadPoolStats.Current.Formatted); Memo1.Lines.Add('------------------------'); end; Repeatedly pressing the button results in the following output: PPL Test --------------- Before: 0 Worker Threads (56 .. 1400) / 0 Idle / 0 Retired / 0 Suspended / Avg. CPU = 0 / Curr. CPU = 0 Finished in 00:00:09.8423686 After: 56 Worker Threads (56 .. 1400) / 43 Idle / 0 Retired / 0 Suspended / Avg. CPU = 30 / Curr. CPU = 100 ------------------------ PPL Test --------------- Before: 56 Worker Threads (56 .. 1400) / 43 Idle / 0 Retired / 0 Suspended / Avg. CPU = 36 / Curr. CPU = 0 Finished in 00:00:04.7841342 After: 65 Worker Threads (56 .. 1400) / 50 Idle / 0 Retired / 0 Suspended / Avg. CPU = 43 / Curr. CPU = 50 ------------------------ PPL Test --------------- Before: 65 Worker Threads (56 .. 1400) / 50 Idle / 0 Retired / 0 Suspended / Avg. CPU = 42 / Curr. CPU = 0 Finished in 00:00:04.7090980 After: 74 Worker Threads (56 .. 1400) / 55 Idle / 0 Retired / 0 Suspended / Avg. CPU = 42 / Curr. CPU = 48 ------------------------ PPL Test --------------- Before: 74 Worker Threads (56 .. 1400) / 55 Idle / 0 Retired / 0 Suspended / Avg. CPU = 38 / Curr. CPU = 0 Finished in 00:00:02.3904253 After: 74 Worker Threads (56 .. 1400) / 50 Idle / 0 Retired / 0 Suspended / Avg. CPU = 57 / Curr. CPU = 98 ------------------------ PPL Test --------------- Before: 74 Worker Threads (56 .. 1400) / 50 Idle / 0 Retired / 0 Suspended / Avg. CPU = 51 / Curr. CPU = 0 Finished in 00:00:05.2865123 After: 84 Worker Threads (56 .. 1400) / 55 Idle / 0 Retired / 0 Suspended / Avg. CPU = 44 / Curr. CPU = 41 ------------------------ PPL Test --------------- Before: 84 Worker Threads (56 .. 1400) / 55 Idle / 0 Retired / 0 Suspended / Avg. CPU = 36 / Curr. CPU = 0 Finished in 00:00:06.8032435 After: 96 Worker Threads (56 .. 1400) / 64 Idle / 0 Retired / 0 Suspended / Avg. CPU = 36 / Curr. CPU = 32 ------------------------ PPL Test --------------- Before: 96 Worker Threads (56 .. 1400) / 64 Idle / 0 Retired / 0 Suspended / Avg. CPU = 0 / Curr. CPU = 0 Finished in 00:00:07.7921758 After: 111 Worker Threads (56 .. 1400) / 77 Idle / 0 Retired / 0 Suspended / Avg. CPU = 27 / Curr. CPU = 23 ------------------------ PPL Test --------------- Before: 111 Worker Threads (56 .. 1400) / 77 Idle / 0 Retired / 0 Suspended / Avg. CPU = 20 / Curr. CPU = 0 Finished in 00:00:02.3559000 After: 111 Worker Threads (56 .. 1400) / 77 Idle / 0 Retired / 0 Suspended / Avg. CPU = 48 / Curr. CPU = 100 ------------------------ PPL Test --------------- Before: 111 Worker Threads (56 .. 1400) / 77 Idle / 0 Retired / 0 Suspended / Avg. CPU = 48 / Curr. CPU = 0 Finished in 00:00:06.6491982 After: 122 Worker Threads (56 .. 1400) / 87 Idle / 0 Retired / 0 Suspended / Avg. CPU = 33 / Curr. CPU = 100 ------------------------ Even after the first run, only 43 threads in the pool seem to have returned to idle. Instead, with almost every further run, the total number of worker threads is increased to make up the difference. Despite this, the total CPU usage is going down slowly. If I wait for the inactive threads to be automatically retired and run the test again, I get something like this: PPL Test --------------- Before: 0 Worker Threads (56 .. 1400) / -36 Idle / 0 Retired / 0 Suspended / Avg. CPU = 0 / Curr. CPU = 0 Finished in 00:00:09.5530676 After: 56 Worker Threads (56 .. 1400) / 20 Idle / 0 Retired / 0 Suspended / Avg. CPU = 30 / Curr. CPU = 100 ------------------------ A negative number of idle threads? And after the run, only 20 threads are stated as idle again. Further runs result in the behavior described above. If someone could shed some light on all this for me, I would highly appreciate it! Am I doing something wrong here? What do I need to do to get 100% CPU usage on every run, without the thread pool ballooning? Thanks! -
AV on finalizing TThreadPool [PPL]
Alexander Pustotin posted a topic in RTL and Delphi Object Pascal
Hi all when switching to Delphi 10.3 Rio I encountered AV in the Parallel Library code. The following somple case can reproduce the issue. I assume that the reason of the problem is a race condition. So it happens in the most of cases but not 100%. program ParallelForAV; {$APPTYPE CONSOLE} uses System.SysUtils, System.Threading; begin try TParallel.&For(1, 200, procedure (AIndex: Integer) begin Abort; end); except { ignore exceptions } end; end. I cannot find this issue at https://quality.embarcadero.com. Could someone confirm the problem? Best regards Alexander- 9 replies
-
- parallel library
- ppl
-
(and 1 more)
Tagged with: