Почему значение переменной цикла не сохраняется в динамически созданном AnonymousThread

У меня есть некоторый академический интерес к тому, как я могу сохранить уникальный идентификатор в динамически созданном файле TThread.

Я создаю что-то вроде этого:

procedure TForm1.Button1Click(Sender: TObject);
var thrn:word;
begin
for thrn := 0 to 5 do//<--- this is a loop variable that should give the unique numbers
  TThread.CreateAnonymousThread(
    procedure()
    var
      i: longint;
      r: double;
      thrns:string;
    begin
      thrns:=inttostr(thrn);//in this thread? variable I try to store the ID as string
      repeat
        for i := 0 to 100000000 do
        begin
          r := random(high(i));//this loop gives some dummy job 
          r := sqr(r);         //to the thread to slow it down
        end;
        TThread.Synchronize(nil,
          procedure()
          begin
            memo1.Text:=memo1.Text+#13#10+
              'done'+thrns;//it returns strange IDs including '6'
          end);
      until false;
    end).Start;
end;

Могу ли я передать динамически созданному потоку уникальный идентификатор, чтобы он мог показать его в своем методе синхронизации?


person asd-tm    schedule 12.12.2015    source источник


Ответы (2)


Это классическое недоразумение. Мы понимаем, что анонимные методы захватывают, но что они захватывают? Значение или переменная?

Ответ последний. Они фиксируют переменную. Существует единственная переменная thrn, которую захватывает каждый из ваших шести анонимных методов. Поскольку существует одна переменная, в любой момент времени существует только одно значение.

Конечно, поскольку вы выполняете код в потоках, у вас возникает гонка данных по этой переменной. Отсюда моя оговорка "в любой момент времени". И именно поэтому у вас есть неповторимые, непредсказуемые результаты. И вы, вероятно, получите доступ к переменной цикла после завершения цикла, и тогда значение не будет определено.

Если вы хотите иметь разные значения для каждого анонимного метода, вы должны создать новую переменную для каждого анонимного метода. Мой ответ на другой вопрос показывает, что: Анонимные методы - захват переменных и захват значений.

Итак, чтобы проиллюстрировать в вашем контексте, нам нужно еще несколько строительных лесов.

function GetThreadProc(thrn: Integer): TProc;
begin
  Result := 
    procedure
    begin
      // thrn is passed by value, so a copy is made, i.e. a new variable
      ....
    end;
end;

....

procedure TForm1.Button1Click(Sender: TObject);
var 
  thrn: Integer;
begin
  for thrn := 0 to 5 do
    TThread.CreateAnonymousThread(
      GetThreadProc(thrn)).Start;
end;
person David Heffernan    schedule 12.12.2015
comment
Спасибо, Дэвид. Странно то, что они могут иметь разные значения... Я получил результаты с id='6' и '2'... - person asd-tm; 12.12.2015
comment
Это потому что у вас гонка данных - person David Heffernan; 12.12.2015
comment
И что переменная thrn не имеет определенного значения после запуска цикла. Потоки все еще работают в то время. - person LU RD; 12.12.2015
comment
Спасибо, Дэвид. Код точно такой же, как @LU-RD написал в своем ответе. Мне нужно обдумать это. Я поддерживаю оба ваших ответа... - person asd-tm; 12.12.2015

Вы должны зафиксировать значение вашего идентификатора. Вот пример, как это сделать.

procedure TForm1.Button1Click(Sender: TObject);
  function GetAnonProc( ID: Word): TProc;
  begin
    Result :=
      procedure
      var
        i: longint;
        r: double;
        thrns:string;
      begin
        thrns:= inttostr(ID);// Capture value
        repeat
          for i := 0 to 100000000 do
          begin
            r := random(high(i));//this loop gives some dummy job
            r := sqr(r);         //to the thread to slow it down
          end;
          TThread.Synchronize(nil,
            procedure()
            begin
              memo1.Text:=memo1.Text+#13#10+
                'done'+thrns;//it returns strange IDs including '6'
            end);
        until false;
      end;

  end;
var
  thrn:word;
  p: TProc;
begin
  for thrn := 0 to 5 do
  begin
    p := GetAnonProc(thrn); // Capture thrn value
    TThread.CreateAnonymousThread(p).Start;
  end;
end;

Приведенный выше код фиксирует 6 разных ссылок на локальную переменную ID. Каждый с разным значением.

Код в вопросе фиксирует ссылку на одну переменную. Поскольку вы не можете контролировать время выполнения потоков, невозможно предсказать, какое значение они получат из ссылки на переменную. Наблюдаемое вами значение 6 связано с тем, что значение переменной цикла не определено после завершения цикла.

Чтобы лучше понять, как работают анонимные методы и как использовать привязку переменных, прочтите Механизм привязки переменных.

person LU RD    schedule 12.12.2015
comment
Спасибо Цзоу за ваше предложение. Однако это не отвечает на вопрос Почему значение переменной цикла не сохраняется... С первого взгляда не должно иметь смысла, используется ли анонимный метод или вы присваиваете его TProc переменную, а затем сразу же передать переменную в качестве параметра. - person asd-tm; 12.12.2015
comment
Смотрите мою правку. В вашем коде используется ссылка на переменную, а не значение. Поэтому, когда потоки начинают работать, они будут печатать любые значения, которые имеет переменная thrn. Его значение не гарантируется после завершения цикла. - person LU RD; 12.12.2015
comment
Я не понимаю. Процедура не обращается к переменной цикла при выполнении потока. Он сохраняет идентификатор в своей собственной строковой переменной при создании и берет значение только из нее. Итак, другими словами... сначала создается поток (и идентификатор сохраняется в собственной переменной потока). Затем поток запускается и использует только значение из своей переменной tread. Я прав? - person asd-tm; 12.12.2015
comment
thrns:=inttostr(thrn); — это ваша первая строка в анонимном методе, так что да, вы обращаетесь к переменной thrn в потоке. Пожалуйста, прочитайте документы, как работают анонимные методы. Они запечатлены в кадре и казнены позже. - person LU RD; 12.12.2015
comment
И только после создания TThread функцией класса TThread.CreateAnonymousThread его результат - TThread запускается методом Start - person asd-tm; 12.12.2015
comment
Код запускается после Старта, да. И в этот момент они получают доступ к ссылкам на переменные, вы должны это понимать. - person LU RD; 12.12.2015
comment
Японял твою точку зрения. Мне нужно подумать и понять, какой смысл присваивать переменную p:TProc и сразу передавать ее в качестве параметра... В чем разница с передачей переменной (референса). Как говорит @David, для всех потоков будет одна переменная p... - person asd-tm; 12.12.2015
comment
В моем примере создаются новые ссылки для каждого значения thrn, вот в чем ключ. - person LU RD; 12.12.2015