6

我想做的事:

我在通用列表中有一些对象。我想以匿名方法捕获每个对象,并将此方法作为单独的 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

如您所见,捕获似乎在主线程中运行良好。但是,我不知道是否已捕获指向对象的指针或仅捕获其 ID 字段?

当我尝试捕获对象并将匿名方法传递给CreateTask函数时,事情变得很奇怪。

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

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

4

2 回答 2

6

The documentation describes what's happening:

Note that variable capture captures variables—not values. If a variable's value changes after being captured by constructing an anonymous method, the value of the variable the anonymous method captured changes too, because they are the same variable with the same storage.

In your code, there is only one LObject variable, so all the anonymous methods you construct refer to it. As your loop makes progress, the value of LObject changes. The tasks haven't gotten a chance to start running yet, so when they do finally run, the loop has terminated and LObject has its final value. Formally, that final value is undefined after the loop.

To capture the value of the loop variable, wrap creation of the task in a separate function:

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;

Then change your loop code:

CreateTask(CreateItemTask(LObject)).Unobserved.Run;
于 2012-11-12T18:41:32.120 回答
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 的调用包裹在一个锁中以清除它。

于 2012-11-12T18:38:07.167 回答