Michael Gendelev
Michael Gendelev

Reputation: 481

Delphi 10: Correct way to run tasks simultaneously

I'm trying to learn how to use delphi parallel library instead of TThread. I have many tasks that I want to run simultaneously. Each task is most time waiting for some event, while its waiting, I'd like to pass excecution to another tasks. Here is my sample code:

type

TObj = class
  pos:     integer;
  Done:    boolean;
  Constructor Create;
  procedure DoWork(Sender: TObject);
end;

var

Objects :   array [0..39] of TObj;
tasks :     array of ITask;

constructor TObj.Create;
begin
  pos:=0;
  DOne:=false;
end;

procedure TObj.DoWork(Sender: TObject);
begin
  repeat
    inc(pos);
    sleep(100);
  until Done;
end;

procedure TForm1.StartClick(Sender: TObject);
var
  i:  integer;
begin
  Setlength (tasks ,Length(Objects));
  for i:=0 to Length(tasks)-1 do begin
    Objects[i]:=TObj.Create; 
    tasks[i] := TTask.Create(Objects[i],Objects[i].DoWork);
    tasks[i].Start;
  end;
  sleep(5000);
  Memo1.Lines.Clear;
  for i:=0 to Length(tasks)-1 do begin
    Objects[i].Done:=true;
    Memo1.Lines.Add(IntToStr(Objects[i].pos));
  end;
end;

I start N Tasks, each should increase its counter by 1 in 100ms. Then I wait 5 sec and check task's values. I excpect them to be at least nearly equal, but real Memo1's output shows first 12 values are about 50, the other 4 values about 20-30 (I'm running Ryzen 1600), and whats strange fo me that the least values are all 0!

Evidently only 16 tasks from 40 actually excecuted atleast once in 5 seconds, so I would like to know how I can replace sleep(100) to actually pass excecution to another tasks?

Upvotes: 2

Views: 5075

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597941

Just because you start N number of tasks does not mean that N number of tasks will be running concurrently. If you want that, stick with TThread.

The PPL uses an internal thread pool to service the TTask objects, and that pool is self-throttling and self-adjusting based on now many CPUs you have installed and how many tasks are actually running. This is explained in the PPL documentation:

The RTL provides the Parallel Programming Library (PPL), giving your applications the ability to have tasks running in parallel taking advantage of working across multiple CPU devices and computers. The PPL includes a number of advanced features for running tasks, joining tasks, waiting on groups of tasks, etc. to process. For all this, there is a thread pool that self tunes itself automatically (based on the load on the CPU’s) so you do not have to care about creating or managing threads for this purpose.

If you create more tasks than the pool has threads, some tasks are going to be waiting for earlier tasks to finish their work. When a given thread finishes a task, it checks for a waiting task, and if found then runs it, repeating until there are no more tasks to run. Multiply that by the number of threads in the pool and the number of tasks in the queue.

So, there will only ever be a handful of tasks running at any given time, and so it may take awhile to get through all of the tasks that you queue up.

If you want more control over the thread pooling, you have to create a TThreadPool object, set its MinWorkerThreads and MaxWorkerThreads properties as desired, and then pass that object to one of the TTask constructors that has an APool input parameter. By default, the MinWorkerThreads is set to TThread.ProcessorCount, and the MaxWorkerThreads is set to TThread.ProcessorCount * 25.

Upvotes: 7

Related Questions