1

我无法理解它是如何工作的。

首先一个非常简单的例子,试图更好地解释我的情况。此代码位于新项目中创建的新表单 Form1 中。mmo1 是一个备忘录组件。

TOb = class
  Name : String;
  constructor Create(Name : String);
  procedure Go();
end;

procedure TOb.Go;
begin
  Form1.mmo1.Lines.Add(Name);
end;

然后我有一个带有此事件的按钮:

procedure TForm1.btn4Click(Sender: TObject);
var
  Index : Integer;
begin
  mmo1.Lines.Clear;
  for Index := 1 to 3 do
    TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(Index)).Go).Start;
end;

我在备忘录上的输出是:
线程 4
线程 4
线程 4

我真的不明白。

第一个问题:为什么“名称”输出是:线程 4?是从 1 到 3 的 For 循环。至少应该是 1 或 3

:为什么它只执行最后一个线程“线程 4”,而不是依次执行“线程 1”、“线程 2”、“线程 3”3 次?

为什么我要问这个?我有一个对象已经有一个正常工作的过程。但是现在我发现我需要处理这个对象的列表。当然可以一个一个地工作,但在我的情况下,它们是彼此独立的,所以我想“嗯,让我们把它们放在线程中,这样它会运行得更快”。

为了避免修改对象以扩展TThread并覆盖Execute,我查找了如何使用过程而不是从 TThread 继承并找到匿名线程的对象来执行线程。对一个对象非常有效,但是当我尝试遍历我的对象列表时,会发生奇怪的行为。

这具有相同的效果。

  for Index := 1 to 3 do
    TThread.CreateAnonymousThread(
      procedure
      var
        Ob : TOb;
      begin
        OB := TOb.Create('Thread ' + IntToStr(Index));
        OB.Go;
      end
    ).Start;

当然我没有清理对象,这只是我正在运行的一些测试。有任何想法吗?或者在这种情况下,我需要从TThread继承并覆盖Execute方法?

有趣的是,运行得很好。

mmo1.Lines.Clear;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(1)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(2)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(3)).Go).Start;

输出:
  线程 1
  线程 2
  线程 3

4

3 回答 3

1

对一个对象非常有效,但是当我尝试遍历我的对象列表时,会发生奇怪的行为。

您可能没有考虑匿名过程如何绑定到变量。尤其是:

请注意,变量捕获捕获变量--not values如果一个变量通过构造匿名方法被捕获后,其值发生了变化,那么匿名方法捕获的变量的值也会发生变化,因为它们是同一个变量,具有相同的存储空间。捕获的变量存储在堆上,而不是堆栈上。

例如,如果您执行以下操作:

var
  Index: Integer;
begin
  for Index := 0 to ObjList.Count-1 do
    TThread.CreateAnonymousThread(TOb(ObjList[Index]).Go).Start;
end;

您实际上会EListError在线程中导致异常(至少在我测试它时 - 我不知道它为什么会发生。通过OnTerminate在调用之前为线程分配一个处理程序来验证Start(),然后让该处理程序检查TThread(Sender).FatalException属性)。

如果您改为这样做:

var
  Index: Integer;
  Ob: TOb;
begin
  for Index := 0 to ObjList.Count-1 do
  begin
    Ob := TOb(ObjList[Index]);
    TThread.CreateAnonymousThread(Ob.Go).Start;
  end;
end;

线程将不再崩溃,但它们很可能对同一个TOb对象进行操作,因为CreateAnonymousThread()正在获取对TOb.Go()方法本身的引用,然后您的循环会Self在每次迭代时修改该引用的指针。我怀疑编译器可能会生成类似这样的代码:

var
  Index: Integer;
  Ob: TOb;
  Proc: TProc; // <-- silently added
begin
  for Index := 0 to ObjList.Count-1 do
  begin
    Ob := TOb(ObjList[Index]);
    Proc := Ob.Go; // <-- silently added
    TThread.CreateAnonymousThread(Proc).Start;
  end;
end;

如果你这样做,它将有一个类似的问题:

procedure StartThread(Proc: TProc);
begin
  TThread.CreateAnonymousThread(Proc).Start;
end;

...

var
  Index: Integer;
  Ob: TOb;
begin
  for Index := 0 to ObjList.Count-1 do
  begin
    Ob := TOb(ObjList[Index]);
    StartThread(Ob.Go);
  end;
end;

可能是因为编译器生成的代码类似于:

procedure StartThread(Proc: TProc);
begin
  TThread.CreateAnonymousThread(Proc).Start;
end;

...

var
  Index: Integer;
  Ob: TOb;
  Proc: TProc; // <-- 
begin
  for Index := 0 to ObjList.Count-1 do
  begin
    Ob := TOb(ObjList[Index]);
    Proc := Ob.Go; // <-- 
    StartThread(Proc);
  end;
end;

不过,这会很好地工作:

procedure StartThread(Ob: TOb);
begin
  TThread.CreateAnonymousThread(Ob.Go).Start;
end;

...

var
  Index: Integer;
  Ob: TOb;
begin
  for Index := 0 to ObjList.Count-1 do
  begin
    Ob := TOb(ObjList[Index]);
    StartThread(Ob);
    // or just: StartThread(TOb(ObjList[Index]));
  end;
end;

通过将调用移动到CreateAnonymousThread()将实际引用隔离TOb.Go()到局部变量的单独过程中,您可以消除在捕获多个对象的引用时发生冲突的任何可能性。

这样匿名程序很有趣。您必须小心它们如何捕获变量。

于 2017-03-07T02:00:10.087 回答
0

在阅读了 Remy Lebeau 在评论中发布的文章,我找到了这个解决方案

通过添加另一个进行调用的过程来更改主对象。更改循环而不是在主循环中创建匿名线程,它是在对象内部创建的。

TOb = class
  Name : String;
  constructor Create(Name : String);
  procedure Process();
  procedure DoWork();
end;

procedure TOb.Process;
begin
  TThread.CreateAnonymousThread(DoWork).Start;
end;

procedure TOb.DoWork;
var
  List : TStringList;
begin
  List := TStringList.Create;
  List.Add('I am ' + Name);
  List.Add(DateTimeToStr(Now));
  List.SaveToFile('D:\file_' + Name + '.txt');
  List.Free;
end;

和循环:

List := TObjectList<TOb>.Create();
List.Add(TOb.Create('Thread_A'));
List.Add(TOb.Create('Thread_B'));
List.Add(TOb.Create('Thread_C'));
List.Add(TOb.Create('Thread_D'));

for Obj in List do
  //TThread.CreateAnonymousThread(Obj.Go).Start;
  Obj.Process;

只需对主对象进行最小更改即可解决问题。

于 2017-03-07T02:11:26.683 回答
0

这与比赛条件有关。当您将最大值增加到 100 时,您将看到不同的值。线程不保证线程何时开始或结束。你可以试试这个代码块。

    for I := 1 to 100 do
  begin
    TThread.CreateAnonymousThread(
    procedure
    var
    Msg : string;
    begin
      try
        Msg := 'This' + I.ToString;
        MessageDlg(Msg,mtCustom,
                                [mbYes,mbAll,mbCancel], 0);
      Except
        on E: Exception do

      End;
    end
    ).Start;
  end;

如果要保证写入 1 到 4,则应在发送到 Thread 之前实例化每个值。

 for I := 1 to 100 do
  begin
    TThread.CreateAnonymousThread(
    procedure
    var
    Msg : string;
    begin
      var instanceValue := I;
      try
        Msg := 'This' + instanceValue.ToString;
        MessageDlg(Msg,mtCustom,
                                [mbYes,mbAll,mbCancel], 0);
      Except
        on E: Exception do

      End;
    end
    ).Start;
  end;
于 2020-08-20T08:09:49.630 回答