1

I have some academic interest of how I can store a unique identifier in a dynamically created TThread.

I create something like this:

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;

Can I pass a unique identifier to the dynamically created thread so that it could show it in its synchronize method?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
asd-tm
  • 3,381
  • 2
  • 24
  • 41

2 Answers2

5

This is a classic misunderstanding. We understand that anonymous methods capture, but what do they capture? The value or the variable?

The answer is the latter. They capture the variable. There is a single variable, thrn, that each of your six anonymous methods capture. Since there is one variable, there is only one value, at any one moment in time.

Of course, since you are executing code in threads, you have a data race on this variable. Hence my "at any one moment in time" proviso. And that's why you have unrepeatable, unpredictable results. And you are likely to access the loop variable after the loop has completed and the value then is undefined.

If you wish to have a different value for each anonymous method, you must make a new variable for each anonymous method. My answer to another question demonstrates that: Anonymous methods - variable capture versus value capture.

So, to illustrate in your context we need some more scaffolding.

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;
Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you, David. The strange thing is that they may have different values... I got results with id='6' and '2'... – asd-tm Dec 12 '15 at 16:19
  • 1
    That's because you have a data race – David Heffernan Dec 12 '15 at 16:19
  • And that the thrn variable does not have a defined value after the loop is run. The threads are still running at that time. – LU RD Dec 12 '15 at 16:20
  • Thank you, David. The code is exacly the same that @LU-RD has written in his answer. I need to think about it. I upvote both your answers... – asd-tm Dec 12 '15 at 16:25
2

You have to capture the value of your identifier. Here is an example how to do that.

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;

The code above captures 6 different references to a local ID variable. Each with a different value.

The code in the question captures a single variable reference. Since you cannot control when the threads are running, there is no way to predict what value they will retrieve from the variable reference. The value 6 you observe is because of the fact that a loop variable's value is undefined after the loop is completed.

To further understand how anonymous methods works and use variable binding, read Variable Binding Mechanism.

LU RD
  • 34,438
  • 5
  • 88
  • 296
  • Thank zou for your proposal. However, it does not answer the question _Why the value of the loop variable is not stored..._ From the first view it should not make sense whether the anonymous method is used or you assign it to a TProc variable and later on immediately pass the variable as a parameter. – asd-tm Dec 12 '15 at 15:53
  • See my edit. The variable reference is used in your code, not the value. So when the threads starts to run they will print whatever values the thrn variable has. An its value is not ensured after the loop is completed. – LU RD Dec 12 '15 at 16:00
  • I do not understand. The procedure does not refer to the loop variable when the thread is run. It stores the id in its own string variable at creation and takes the value only from it. So, in other words... at first the thread is created (and the id is stored in thread's own variable). Next the thread is run and uses only the value from its tread variable. Am I right? – asd-tm Dec 12 '15 at 16:00
  • `thrns:=inttostr(thrn);` is your first line in the anonymous method, so yes you are accessing the thrn variable in the thread. Please read the docs how anonymous methods works. They are captured in a frame and executed later. – LU RD Dec 12 '15 at 16:02
  • And only after the TThread is created by `TThread.CreateAnonymousThread` class function its result - `TThread` is run by `Start` method – asd-tm Dec 12 '15 at 16:04
  • The code is run after the Start, yes. And at that point they access the variable references, you have to understand that. – LU RD Dec 12 '15 at 16:07
  • I got your point. I need to think and understand what is the sense to assgn the p:TProc variable and immediately pass it as a parameter... What is the difference with passing the variable (reference). There will be a single variable p for all the threads as @David says... – asd-tm Dec 12 '15 at 16:17
  • My example produces new references for each thrn value, that is the clue here. – LU RD Dec 12 '15 at 16:19