我想做什么:
我在genric列表中有一些对象。我想以匿名方法捕获每个对象,并将此方法作为单独的OTL任务执行。
这是一个简化的例子:
program Project51;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections, OtlTaskControl, OtlTask;
type
TProc = reference to procedure;
type
TMyObject = class(TObject)
public
ID: Integer;
constructor Create(AID: Integer);
end;
constructor TMyObject.Create(AID: Integer);
begin
ID := AID;
end;
var
Objects: TList<TMyObject>;
LObject: TMyObject;
MyProc: TProc;
begin
Objects := TList<TMyObject>.Create;
Objects.Add(TMyObject.Create(1));
Objects.Add(TMyObject.Create(2));
Objects.Add(TMyObject.Create(3));
for LObject in Objects do
begin
//This seems to work
MyProc := procedure
begin
Writeln(Format('[SameThread] Object ID: %d',[LObject.ID]));
end;
MyProc;
//This doesn't work, sometimes it returns 4 lines in console!?
CreateTask(
procedure(const Task: IOmniTask)
begin
Writeln(Format('[Thread %d] Object ID: %d',[Task.UniqueID, LObject.ID]));
end
).Unobserved.Run;
end;
Sleep(500); //Just wait a bit for tasks to finish
Readln;
end.
这就是结果:
正如您所看到的,捕获似乎在主线程中正常工作。但是,我不知道是否已捕获指向对象的指针或仅捕获其ID字段?
当我尝试捕获对象并将匿名方法传递给CreateTask
时,事情变得很奇怪。
首先,似乎只捕获了TMyObject
的第三个实例。其次,我在控制台日志中有四条消息,尽管我在通用列表中只有三个对象。第二种行为是不一致的,有时我在控制台中有三条消息,有时候我有四条消息。
请解释我上面提到的两个问题的原因,并提出一个消除问题的解决方案,并允许我将每个对象实例传递给单独的OTL任务。 (我不想使用常规TThread
课程。)
答案 0 :(得分:5)
The documentation describes what's happening:
请注意,变量捕获捕获变量 -not 值。如果变量的值在通过构造匿名方法捕获后发生更改,则捕获的匿名方法的变量值也会更改,因为它们是具有相同存储的相同变量。
在您的代码中,只有一个LObject
变量,因此 all 您构造的匿名方法引用它。随着循环的进展,LObject
的值会发生变化。这些任务还没有机会开始运行,所以当它们最终运行时,循环终止并且LObject
具有其最终值。形式上,循环后最终值是未定义的。
要捕获循环变量的值,请在单独的函数中创建任务:
function CreateItemTask(Obj: TMyObject): TOmniTaskDelegate;
begin
Result := procedure(const Task: IOmniTask)
begin
Writeln(Format('[Thread %d] Object ID: %d',[Task.UniqueID, Obj.ID]));
end;
end;
然后更改循环代码:
CreateTask(CreateItemTask(LObject)).Unobserved.Run;
答案 1 :(得分:1)
匿名过程捕获变量而不是值。所以你正在捕获变量LObject。由于这是一个循环变量,因此LObject的值会发生变化。匿名过程在执行时评估LObject,而不是在创建匿名过程时评估。
我可能只使用TMyObject的方法,而不是使用匿名过程。尝试以这种方式编写代码,我预测你会发现它更容易理解。
procedure TMyObject.TaskProc(const Task: IOmniTask);
begin
Writeln(Format('[Thread %d] Object ID: %d', [Task.UniqueID, Self.ID]));
end;
获得4行输出而不是3行的原因可能只是WriteLn不是线程安全的。将对WriteLn的调用包含在锁中以清除它。