Как я могу захватывать переменные анонимным методом при использовании его в OTL?

Что я хочу сделать:

У меня есть несколько объектов в списке родов. Я хочу захватить каждый из этих объектов в анонимном методе и выполнить этот метод как отдельную задачу 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 учебный класс.)

2 ответа

Решение

Документация описывает, что происходит:

Обратите внимание, что захват переменных захватывает переменные, а не значения. Если значение переменной изменяется после захвата путем создания анонимного метода, значение переменной, захваченной анонимным методом, также изменяется, потому что это одна и та же переменная с одинаковым хранилищем.

В вашем коде есть только один 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;

Затем измените код вашего цикла:

CreateTask(CreateItemTask(LObject)).Unobserved.Run;

Анонимные процедуры фиксируют переменные, а не значения. Итак, вы захватываете переменную 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 в замок, чтобы очистить это.

Другие вопросы по тегам