peiman F.
peiman F.

Reputation: 1658

delphi variable value is changing at thread in a loop

my code is running a for loop to process some data like here

procedure printValue(Value: Integer);
begin
  TThread.Synchronize(TThread.Current, procedure
  begin
   form1.memo1.lines.add( Value.ToString );
  end);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  myThread : TThread;
  Proc1: TMyProc;
begin
  for I := 0 to 10 do
  begin
    myThread := TThread.CreateAnonymousThread(
    procedure
    begin
      printValue( i );
    end);
    myThread.Start;
  end;
end;

this code out put is like this:

3
5
6
8
9
11
10
4
11
4
7

this is not good so i add a small delay like sleep(1) after thread start.this will fix output problem but not a good idea because in a large loop block the ui thread so try to use this document as help so my code changed like this:

function CaptureValue(Value: Integer): TMyProc;
begin
  Result := procedure begin Writeln(Value); end;
end;

procedure printValue(Value: Integer);
begin
  TThread.Synchronize(TThread.Current, procedure
  begin
   form1.memo1.lines.add( Value.ToString );
  end);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  myThread : TThread;
  Proc1: TMyProc;
begin
  for I := 0 to 10 do
  begin

    myThread := TThread.CreateAnonymousThread(
    procedure
    begin
      Proc1:= CaptureValue(i);
      printValue( Proc1 );
    end);
    myThread.Start;
  end;
end;

but i got [dcc32 Error] Unit11.pas(57): E2010 Incompatible types: 'Integer' and 'procedure, untyped pointer or untyped parameter' error.

what is wrong with my code?

Upvotes: 3

Views: 1232

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596176

Anonymous procedures capture variables, not values. This is documented behavior. So in both of your examples, your threads are sharing a single I variable with no synchronization over the access of that variable.

You had the right idea to pass I to a procedure and then capture it from the argument list before using it. However, you went about it the wrong way.

The reason your second example actually fails to compile is because you are misusing CaptureValue(). It returns a TMyProc, which is clearly a reference to procedure (which BTW, the RTL already has a TProc for that same purpose). You are passing an anonymous procedure as-is to printValue(), which takes an Integer instead. That is what the compiler error is complaining about.

The way you are using the return value, CaptureValue() would have to instead return an anonymous function that itself returns an Integer (ie, have CaptureValue() return a reference to function: Integer, aka TFunc<Integer>), then call that function and pass that return value to PrintValue().

You are still capturing I itself before passing it to CaptureValue(), though, so you still have threads sharing I. You would need to call CaptureValue() from inside the loop directly before calling CreateAnonymousThread(). But then, your threads would be capturing and sharing the Proc1 variable instead, so you would be right back to the original problem.

With that said, try something more like this instead:

procedure PrintValue(Value: Integer);
begin
  TThread.Synchronize(nil,
    procedure
    begin
      Form1.Memo1.Lines.Add( Value.ToString );
    end
  );
end;

function CaptureAndPrintValue(Value: Integer): TProc;
begin
  Result := procedure
    begin
      printValue( Value );
    end
  );
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  for I := 0 to 10 do
  begin
    TThread.CreateAnonymousThread(
      CaptureAndPrintValue(I)
    ).Start;
  end;
end;

Or, you can let the RTL handle the threading for you:

uses
  ..., System.Threading;

procedure PrintValue(Value: Integer);
begin
  // can't use TThread.Synchronize() with TParallel, as it
  // doesn't service the main message queue while looping...
  TThread.Queue(nil,
    procedure
    begin
      Form1.Memo1.Lines.Add( Value.ToString );
    end
  );
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TParallel.For(0, 10,
    procedure(I: Integer)
    begin
      PrintValue(I);
    end
  );
end;

Upvotes: 6

Related Questions