在OTL中使用变量时,如何通过匿名方法捕获变量?

Wod*_*dzu 5 delphi closures anonymous-methods delphi-2009 omnithreadlibrary

我想做的事:

我在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.
Run Code Online (Sandbox Code Playgroud)

这就是结果:

捕获的对象ID

正如您所看到的,捕获似乎在主线程中正常工作.但是,我不知道是否已捕获指向对象的指针或仅捕获其ID字段?

当我尝试捕获对象并将匿名方法传递给CreateTask函数时,事情就变得怪异了.

首先,TMyObject似乎只捕获了第三个实例.其次,我在控制台日志中有四条消息,尽管我在通用列表中只有三个对象.第二种行为是不一致的,有时我在控制台中有三条消息,有时候我有四条消息.

请解释上面提到的两个问题的原因,并提出一个消除问题的解决方案,并允许我将每个对象实例传递给单独的OTL任务.(我不想使用常规TThread课程.)

Rob*_*edy 5

文档描述了正在发生的事情:

请注意,变量捕获捕获变量 - 不是.如果变量的值在通过构造匿名方法捕获后发生更改,则捕获的匿名方法的变量值也会更改,因为它们是具有相同存储的相同变量.

在您的代码中,只有一个LObject变量,因此您构造的所有匿名方法都会引用它.随着循环的进展,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;
Run Code Online (Sandbox Code Playgroud)

然后更改循环代码:

CreateTask(CreateItemTask(LObject)).Unobserved.Run;
Run Code Online (Sandbox Code Playgroud)