iTask-将参数值赋给匿名过程 [英] iTask - values parameters to anonymous procedure
问题描述
我需要创建一定数量的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.total
和recarray.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;
由于mylocalrec
和pmyrec
是记录(值类型),它们的内容将占据两个不同的存储位置.以上分配会将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;
由于total
和average
也是值类型,因此它们的内容将被复制,并且从此刻起,对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.
为什么将动态数组字段vet
从pmyrec
分配给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.
计算total
和average
不是线程安全的.
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屋!