iTask-将参数值赋给匿名过程 [英] iTask - values parameters to anonymous procedure

查看:73
本文介绍了iTask-将参数值赋给匿名过程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要创建一定数量的iTask,以在动态数组和记录中的其他字段中执行操作.每个iTask在此阵列的特定部分中运行.数组是记录中的字段,该字段作为var参数传递给iTask.

I need to create a certain number of iTasks to perform operations in a dynamic array and other fields in a record. Each iTask operates in a specific portion of this array. The array is a field in a record which is passed as a var parameter to the iTask.

array字段中的操作进展顺利,但是在所有任务完成工作后,其他记录字段未返回任何值.在另一个仅在阵列上运行且仍有效的问题中,我曾从Dalija寻求帮助,但现在我在其他领域遇到了麻烦.

The operations in the array field is going well, however the other record fields don't return any value after all tasks finish their work. I had help from Dalija in another question that operates only on the array and it worked, but now I'm having trouble with other fields.

这是我的代码:

program ProjectTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.Threading;

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

// (1) ===> here is the major procedure that populates the dyn. array and
// calculates other two fields  :  myrec.total  and myrec.avg

procedure ProcA(const pin, pfin: integer; var Prec: myrec);
var
  vind: integer;
begin
  for vind := pin to pfin do
    begin
      Prec.vet[vind] := vind * 10;
      Prec.total := Prec.total + Prec.vet[vind];        // sum all array values
    end;
  Prec.average := Trunc(Prec.total / Length(Prec.vet)); // calculates the average

end;

// (2)  Here iTask is created and calls ProcA

function CreateTask(first, last: integer; var Pmyrec: myrec): ITask;

var
  mylocalrec: myrec;

begin
  mylocalrec := Pmyrec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec)
    end);
end;

procedure Test;
var
  Recarray: myrec;
  Ptasks: array of ITask;
  vind, indtask, vslice: integer;
  vfirst, vlast, vthreads, vsize: integer;
begin

  vthreads := 4;
  vsize := 16;

  SetLength(Ptasks, vthreads);
  SetLength(Recarray.vet, vsize);

  // Initialize the array , just to check after iTask execution
  for vind := low(Recarray.vet) to high(Recarray.vet) do
    Recarray.vet[vind] := -33;

  // initialize the sum and average field just to check after iTask execution
  Recarray.total := -1;
  Recarray.average := -2;

  // portion of array to scan for each iTask
  vslice := Length(Recarray.vet) div vthreads;

  for indtask := low(Ptasks) to high(Ptasks) do
    begin
      vfirst := indtask * vslice;
      vlast := (indtask + 1) * vslice - 1;

      if (Length(Recarray.vet) mod vthreads <> 0) and (indtask = high(Ptasks)) then vlast := high(Recarray.vet);

      Ptasks[indtask] := CreateTask(vfirst, vlast, Recarray);
    end;

  // Starting all Tasks
  for indtask := low(Ptasks) to high(Ptasks) do
    Ptasks[indtask].Start;

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

  // (3) Here it is listed the array contents and it is ok
  for vind := low(Recarray.vet) to high(Recarray.vet) do
      Writeln(' Array position  : ' + Format('%.3d', [vind]) + '   content  : ' + Recarray.vet[vind].tostring);

  Writeln(' =========================================================');

// (4) Here is is listed fields recarray.total and recarray.avg and they were not
// processed inside the iTask .    I expected to see the computed values for those fields

  Writeln(' Array sum   : ' + Format('%.0d', [Recarray.total]) + '    Array average   : ' + Format('%5.2n', [Recarray.average * 1.0]));

end;

begin
  Test;
  Readln;
end.

输出为:

 Array position  : 000   content  : 0
 Array position  : 001   content  : 10
 Array position  : 002   content  : 20
 Array position  : 003   content  : 30
 Array position  : 004   content  : 40
 Array position  : 005   content  : 50
 Array position  : 006   content  : 60
 Array position  : 007   content  : 70
 Array position  : 008   content  : 80
 Array position  : 009   content  : 90
 Array position  : 010   content  : 100
 Array position  : 011   content  : 110
 Array position  : 012   content  : 120
 Array position  : 013   content  : 130
 Array position  : 014   content  : 140
 Array position  : 015   content  : 150
 =========================================================
 Array sum   : -1    Array average   : -2,00

问题是:运行所有iTask之后,只有动态数组字段recarray.vet包含正确的值.与运行iTask之前一样,字段recarray.totalrecarray.average仍包含其初始值.

The problem is: after running all the iTasks, only the dynamic array field recarray.vet contain correct values. The fields recarray.total and recarray.average still contain their initial values as before the iTask have run.

如何正确更新这些字段中的值,以便在任务完成运行后它们将包含正确的值?

How to correctly update values in those fields, so they will contain correct values after tasks finish running?

推荐答案

虽然您的代码似乎只有一个问题-如何用任务填充记录中的整数字段-当您解决一个问题时,您将遇到另一个问题-从多个线程读取和写入相同的内存位置.

While it seems that your code has only one problem - how to populate integer fields in a record with tasks - when you solve that one you will have another issue - reading and writing same memory location from multiple threads.

1.如何在记录中填充整数字段?

记录是值类型,动态数组是引用类型.这就是为什么您的代码可以更新数组中的值但不能更新记录中的值的原因.

Records are value types and dynamic arrays are reference types. That is the reason why your code can update values in array but it cannot update values in record.

让我们看看这里发生了什么.

Let's see what is happening here.

function CreateTask(first, last: integer; var pmyrec: myrec): ITask;
var
  mylocalrec: myrec;
begin
  mylocalrec := pmyrec;

由于mylocalrecpmyrec是记录(值类型),它们的内容将占据两个不同的存储位置.以上分配会将pmyrec内容复制到mylocalrec.

Since mylocalrec and pmyrec are records (value types) their content will occupy two different memory locations. Above assignment will copy the content of the pmyrec into mylocalrec.

这等同于以下内容:

mylocalrec.vet := pmyrec.vet;
mylocalrec.total := pmyrec.total;
mylocalrec.average := pmyrec.average;

由于totalaverage也是值类型,因此它们的内容将被复制,并且从此刻起,对mylocalrec中的那些整数字段之一进行的任何更改都不会对原始pmyrec产生任何影响.这就是您的代码失败的原因.

Since total and average are also value types their content will be copied and from this point on any change to either of those integer fields in mylocalrec will not have any effect to the original pmyrec. This is why your code is failing.

为什么将动态数组字段vetpmyrec分配给mylocalrec有用?

Why assigning dynamic array field vet from pmyrec to mylocalrec works?

因为动态数组是引用类型-从一个变量到另一个变量的赋值仅复制引用(指针)的值,而不是实际内容.两个vet变量都将指向开始运行任务之前分配的同一数组.

Because dynamic arrays are reference types - assignment from one variable to another only copies value of the reference (pointer) and not the actual content. Both vet variables will point to the same array you have allocated before you started running your tasks.

要解决上述问题,您必须传递某种引用类型而不是值类型.最简单的事情是声明记录指针类型并传递该指针类型.

To solve above issue you have to pass some reference type instead of value type. The simplest thing is declaring record pointer type and passing that one.

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

  pmyrec = ^myrec;


function CreateTask(first, last: integer; rec: pmyrec): ITask;
var
  mylocalrec: pmyrec;
begin
  mylocalrec := rec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec^)
    end);
end;

...
Ptasks[indtask] := CreateTask(vfirst, vlast, @Recarray);

2.如何解决线程问题?

解决原始问题后,您将遇到线程问题.从多个线程读取和写入相同的内存位置是不安全的.您得到的结果可能不正确.尽管您可以运行数千次代码,并且所有值可能都是正确的,但迟早您会遇到它们不正确的情况.

After you have solved original issue you will experience threading issue. Reading and writing same memory location from multiple threads is not safe. Results you will get may be incorrect. While you can run your code thousand times and all values might be correct, sooner or later you will run into situation where they will not.

在您的情况下,填充动态数组是安全的,因为每个线程在数组的不同部分上运行,并且在您的任务运行时不会重新分配数组(其大小不会更改).您的那部分代码是线程安全的.

Populating dynamic array is safe in your case, because each thread operates on distinct part of the array and the array is not reallocated (its size is not changed) while your tasks run. That part of your code is thread safe.

计算totalaverage不是线程安全的.

Calculating total and average is not thread safe.

您必须将此类代码与主线程同步-在这种情况下,所有读取和写入操作均将从主线程完成,并且您将获得正确的结果.或者您必须在所有任务完成后运行此类代码.哪种情况更适合特定情况.

You have to synchronize such code with the main thread - in that case all reading and writing will be done from main thread and you will get correct results. Or you have to run such code after all tasks are finished. Whichever is more suitable for particular case.

procedure ProcA(const pin, pfin: integer; prec: pmyrec);
var
  vind: integer;
  total: integer;
begin
  total := 0;
  for vind := pin to pfin do
    begin
      prec.vet[vind] := vind * 10;
      total := total + prec.vet[vind];        // sum all array values
    end;

  TThread.Synchronize(nil,
    procedure
    begin
      prec.total := prec.total + total;
      prec.average := Trunc(prec.total / Length(prec.vet)); // calculates the average
    end);
end;

但是,在任务中与主线程同步将导致使用TTask.WaitForAll方法的死锁,在您的情况下,该方法也会从主线程运行.要解决这一部分,您还必须从另一个线程运行整个Test方法.

However, synchronizing with the main thread within the tasks will cause the deadlock with TTask.WaitForAll method that also runs from the main thread in your case. To solve that part, you also have to run whole Test method from another thread.

  TTask.Run(
    procedure
    begin
      Test;
    end);

当我们将所有这些部分放在一起时,完整的代码将是:

And when we put all those parts together, complete code would be:

program ProjectTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.Classes,
  System.Threading;

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

  pmyrec = ^myrec;

// (1) ===> here is the major procedure that populates the dyn. array and
// calculates other two fields  :  myrec.total  and myrec.avg

procedure ProcA(const pin, pfin: integer; prec: pmyrec);
var
  vind: integer;
  total: integer;
begin
  total := 0;
  for vind := pin to pfin do
    begin
      prec.vet[vind] := vind * 10;
      total := total + prec.vet[vind];        // sum all array values
    end;

  TThread.Synchronize(nil,
    procedure
    begin
      prec.total := prec.total + total;
      prec.average := Trunc(prec.total / Length(prec.vet)); // calculates the average
    end);
end;

// (2)  Here iTask is created and calls ProcA

function CreateTask(first, last: integer; rec: pmyrec): ITask;
var
  mylocalrec: pmyrec;
begin
  mylocalrec := rec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec);
    end);
end;

procedure Test;
var
  Recarray: myrec;
  Ptasks: array of ITask;
  vind, indtask, vslice: integer;
  vfirst, vlast, vthreads, vsize: integer;
begin

  vthreads := 4;
  vsize := 16;

  SetLength(Ptasks, vthreads);
  SetLength(Recarray.vet, vsize);

  // Initialize the array , just to check after iTask execution
  for vind := low(Recarray.vet) to high(Recarray.vet) do
    Recarray.vet[vind] := -33;

  // initialize the sum and average field just to check after iTask execution
  Recarray.total := -1;
  Recarray.average := -2;

  // portion of array to scan for each iTask
  vslice := Length(Recarray.vet) div vthreads;

  for indtask := low(Ptasks) to high(Ptasks) do
    begin
      vfirst := indtask * vslice;
      vlast := (indtask + 1) * vslice - 1;

      if (Length(Recarray.vet) mod vthreads <> 0) and (indtask = high(Ptasks)) then vlast := high(Recarray.vet);

      Ptasks[indtask] := CreateTask(vfirst, vlast, @Recarray);
    end;

  // Starting all Tasks
  for indtask := low(Ptasks) to high(Ptasks) do
    Ptasks[indtask].Start;

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

  // (3) Here it is listed the array contents and it is ok
  for vind := low(Recarray.vet) to high(Recarray.vet) do
      Writeln(' Array position  : ' + Format('%.3d', [vind]) + '   content  : ' + Recarray.vet[vind].tostring);

  Writeln(' =========================================================');

// (4) Here is is listed fields recarray.total and recarray.avg and they were not
// processed inside the iTask .    I expected to see the computed values for those fields

  Writeln(' Array sum   : ' + Format('%.0d', [Recarray.total]) + '    Array average   : ' + Format('%5.2n', [Recarray.average * 1.0]));

end;

begin
  TTask.Run(
    procedure
    begin
      Test;
    end);
end.

这篇关于iTask-将参数值赋给匿名过程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆