首页 > 解决方案 > delphi变量值在循环中的线程处发生变化

问题描述

我的代码正在运行一个 for 循环来处理像这里这样的一些数据

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;

输出的代码是这样的:

3
5
6
8
9
11
10
4
11
4
7

这不好,所以我sleep(1)在线程启动之后添加了一个小的延迟。这将解决输出问题,但不是一个好主意,因为在一个大循环中阻塞了 ui 线程,所以尝试使用这个文档作为帮助,所以我的代码更改如下:

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;

但我得到了[dcc32 Error] Unit11.pas(57): E2010 Incompatible types: 'Integer' and 'procedure, untyped pointer or untyped parameter'错误。

我的代码有什么问题?

标签: delphi

解决方案


匿名过程捕获变量,而不是值。这是记录在案的行为。因此,在您的两个示例中,您的线程都共享一个I变量,而对该变量的访问没有同步。

您有正确的想法传递I给一个过程,然后在使用它之前从参数列表中捕获它。然而,你走错了路。

您的第二个示例实际上无法编译的原因是您滥用CaptureValue(). 它返回 a TMyProc,这显然是 a reference to procedure(顺便说一句,RTL 已经有 aTProc用于相同目的)。您正在将一个匿名过程按原样传递给printValue(),而它需要一个Integer。这就是编译器错误所抱怨的。

您使用返回值的方式CaptureValue()必须改为返回一个匿名函数,该函数本身返回一个Integer(即,CaptureValue()返回一个reference to function: Integer,又名TFunc<Integer>),然后调用该函数并将返回值传递给PrintValue().

I但是,在将其传递给 之前,您仍在捕获自身CaptureValue(),因此您仍然有线程共享I。您需要在调用CaptureValue()之前直接从循环内部调用CreateAnonymousThread(). 但是,您的线程将Proc1改为捕获和共享变量,因此您将立即回到最初的问题。

话虽如此,请尝试更像这样的东西:

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;

或者,您可以让 RTL 为您处理线程:

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;

推荐阅读