iTask-如何将变量用作TTask过程的参数

时间:2018-07-11 03:50:56

标签: multithreading delphi task

我需要创建多个iTask,这些iTask会将相同的数组填充到不同的位置。由于每个任务要执行的代码相同,因此我决定创建一个iTasks数组并创建4个任务。将参数传递给iTask内部的主要过程时遇到问题。当我使用变量作为参数时,仅考虑最后创建的Task的值。当我将参数作为值(硬编码)传递时,它将尊重每个任务的所有值。请查看我的代码:

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;

==>此下面的方法BTHardCodedClick产生预期的结果。它相应地填充数组。但是,在创建4个iTask和在ProcA中传递参数时,进行了硬编码。我不想用这种方式实现!

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

===>以下方法是我要实现的一种方法,但是它没有工作!,因为它没有填充数组。似乎仅执行了最后一个iTask“ PTasks [indtask]”。

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 我不明白为什么在iTask中对procA(vfirst,vlast,vlast,vsize,indtask,varray)的调用没有考虑参数vfirst,vlast的值。 预先感谢您的帮助!

1 个答案:

答案 0 :(得分:5)

您正在观察的效果归因于匿名方法变量捕获机制。它不会在代码执行过程中的特定点捕获变量值,而是捕获变量的位置。

由于所有任务都在创建它们的循环之后运行,因此您只会看到存储的最后一个值。

要解决您的问题,您必须添加其他功能,以确保不捕获任务中的常见变量。

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;

然后您称呼它

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

当然,如果需要,您也可以删除ProcA过程并将其逻辑直接合并到CreateTask函数中。