iTask - how to use variables as parameters to TTask procedure

I need to create a number of iTasks that will populate the same array in different positions. Since the code to be performed for each Task is the same, I decided to create an array of iTasks and created 4 tasks. I got a problem when passing parameters to the major procedure inside the iTask. when I use variables as parameters , only the values of the last Task created are being considered. When I pass the parameters as values (hard-coded) it respect all values for each task. Please see my code :

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  UNTThreads, Vcl.StdCtrls,
  System.Threading ;

type
 Vet         = array of integer;

type
  TFMThreadArray = class(TForm)
    EDTArraySize: TEdit;
    EDTNumberofThreads: TEdit;
    Memo1: TMemo;
    LBArraySize: TLabel;
    LBThreads: TLabel;
    BTUsingForLoop: TButton;
    EDTThread: TEdit;
    BTHardCoded: TButton;

    procedure BTUsingForLoopClick(Sender: TObject);
    procedure BTHardCodedClick(Sender: TObject);

  private
    { Private declarations }


    procedure  ProcA ( Const  pin, pfin, Psize, Ptask  : integer;
                       Var   Parray : vet);

  public
    { Public declarations }
  end;

var
  FMThreadArray: TFMThreadArray;

implementation

{$R *.dfm}


// Procedure to be called by each iTask    
    procedure TFMThreadArray.ProcA ( Const  pin, pfin, Psize, Ptask  : integer;
                                      Var   Parray : vet);
    var
        vind : integer;

    begin
          for vind := pin to pfin do
          begin
               Parray[vind]   := vind * 10;
          end;
    end;

==> This below method, BTHardCodedClick, produces the expected result. It populates the array accordingly. BUT it is hard coded in creating 4 iTasks and in passing parameters in ProcA. I don't want to implement this way !

procedure TFMThreadArray.BTHardCodedClick(Sender: TObject);

var
    varray                   : vet;
    ind, indtask             : Integer;
    Ptasks                   : array of iTask;

begin
     memo1.Clear;

     SetLength(PTasks,Strtoint(EDTNumberofThreads.text));
     SetLength(varray,StrToint(EDTarraysize.text));

     // fill array with a initial value -2
     for ind := Low(varray) to High(varray) do
            varray[ind] :=-2;


     // when call ProcA passing values parameters it works propperly
     PTasks[0] := TTask.Create( procedure
                                      begin
                                             ProcA(0,3,16,0,varray) ;
                                      end
                                     ) ;

     PTasks[1] := TTask.Create( procedure
                                      begin
                                             ProcA(4,7,16,1,varray) ;
                                      end
                                     ) ;

     PTasks[2] := TTask.Create( procedure
                                      begin
                                             ProcA(8,11,16,2,varray) ;
                                      end
                                     ) ;

     PTasks[3] := TTask.Create( procedure
                                      begin
                                             ProcA(12,15,16,3,varray) ;
                                      end
                                     ) ;


     for Indtask := Low(Ptasks) to High(Ptasks) do
         Ptasks[Indtask].Start;

     TTask.WaitForAll(Ptasks);

     memo1.Clear;
     memo1.Lines.Add(' ============== Creating TASKs with hard-coded parameters ===============');
     memo1.lines.add(' Array size : '        + EDTArraySize.text +
                      ' number of Tasks : ' + EDTNumberofThreads.text);
     memo1.Lines.Add(' =========================================================');

     for ind := Low(varray) to High(varray) do
          memo1.Lines.Add(' Array position  : '    + Format('%.3d',[ind])  +
                          '   content  : ' + varray[ind].ToString   );

end;

enter image description here

===> The following method is the one I want to implement BUT it is not working !, because it is not populating the array. It seems that only the last iTask " PTasks[indtask]" is being performed.

procedure TFMThreadArray.BTUsingForLoopClick(Sender: TObject);
var
    varray                           : vet;
    Ptasks                           : array of iTask;
    vind, indtask, vslice            : Integer;
    vfirst, vlast, vthreads, vsize   : Integer;

begin

     vthreads := Strtoint(EDTNumberofThreads.text);
     vsize    := StrToint(EDTArraysize.text);

     SetLength(PTasks,vthreads);
     SetLength(varray,vsize);

     for vind := Low(varray) to High(varray) do
            varray[vind]:=-33;

     vslice := Length(varray) div vthreads;


     for indtask := Low(PTasks) to High(PTasks) do
     begin
          vfirst := indtask       * vslice;
          vlast  := (indtask + 1) * vslice - 1;

          if (Length(varray) mod vthreads <> 0) and (indtask = High(Ptasks)) then
               vlast := HIgh(varray);

          PTasks[indtask] := TTask.Create( procedure
                                            begin
                                                  procA(vfirst,vlast,vsize,indtask,varray) ;
                                            end
                                           ) ;
     end;

     // Starting all Tasks
     for Indtask := Low(Ptasks) to High(Ptasks) do
         Ptasks[Indtask].Start;

     // Waits until all Tasks been concluded
     TTask.WaitForAll(Ptasks);

     memo1.Clear;
     memo1.Lines.Add(' =============  Using For Loop to create the TASKs =====================');
     memo1.lines.add(' Array size : '        + EDTArraySize.text +
                      ' number of Tasks : ' + EDTNumberofThreads.text);
     memo1.Lines.Add(' =========================================================');


     for vind := Low(varray) to High(varray) do
         memo1.Lines.Add(' Array position  : '    + Format('%.3d',[vind])  +
                         '   content  : '         + varray[vind].ToString   );

end;

end.

enter image description here I can't understand why a call to procA(vfirst,vlast,vsize,indtask,varray) inside the iTask is not considering the values of parameters vfirst, vlast. Thanks in advance for your help !

1 answer

  • answered 2018-07-11 08:32 Dalija Prasnikar

    The effect you are observing is due to anonymous method variable capture mechanism. It does not capture variable values at specific point during code execution, but location of the variables.

    Since all tasks run after the loop where you create them, you will see only the last value stored.

    To solve your problem you have to add additional function ensuring that you don't capture common variables in your task.

    function CreateTask(vfirst, vlast, vsize, indtask: integer; var varray: Vet): ITask;
    var
      va: Vet;
    begin
      // var parameter cannot be captured so we have to store it into
      // local variable - dynamic arrays act like pointers and any changes
      // to local variable will actually change the original too        
      va := varray;
      Result := TTask.Create(
            procedure
            begin
              ProcA(vfirst, vlast, vsize, indtask, va);
            end);
    end;
    

    And then you call it like

      Ptasks[indtask] := CreateTask(vfirst, vlast, vsize, indtask, varray);
    

    Of course, you can also remove your ProcA procedure and incorporate its logic directly inside CreateTask function if that suits your needs.