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関数に渡そうとすると、物事がおかしくなります。

まず第一に、の3番目のインスタンスだけTMyObjectがキャプチャされたように見えました。次に、汎用リストにオブジェクトが3つしかないにもかかわらず、コンソールログに4つのメッセージがあります。2番目の動作には一貫性がなく、コンソールに3つのメッセージが表示されることもあれば、4つのメッセージが表示されることもあります。

上記の2つの問題の理由を説明し、問題を排除し、オブジェクトの各インスタンスを個別のOTLタスクに渡すことができるソリューションを提案してください。TThread(通常のクラスは使いたくないです。)

4

2 に答える 2

6

ドキュメントには、何が起こっているかが説明されています

変数キャプチャは、ではなく変数をキャプチャすることに注意してください。匿名メソッドを作成してキャプチャした後に変数の値が変わると、同じストレージを持つ同じ変数であるため、匿名メソッドがキャプチャした変数の値も変わります。

コードにはLObject変数が1つしかないため、作成するすべての匿名メソッドがそれを参照します。ループが進むにつれて、値が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;
于 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;

3行ではなく4行の出力を取得する理由は、おそらくWriteLnがスレッドセーフではないためです。WriteLnの呼び出しをロックでラップして、それをクリアします。

于 2012-11-12T18:38:07.167 に答える