TThread.CreateAnonymousThread的奇怪行为

时间:2017-03-07 00:40:03

标签: delphi-xe2 tthread

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

首先是一个非常简单的例子,尝试更好地解释我的情况。 此代码位于新项目中创建的Form 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?是For循环从1到3.至少应该是1或3

第二:为什么它只执行最后一个线程“线程4”,而不是按顺序执行3次“线程1”,“线程2”,“线程3”?

为什么我问这个?我有一个对象,已经有一个正常的进程。但是现在我发现我的情况是需要处理这个对象的List。确实工作很好,一个接一个,但在我的情况下,他们是独立的,所以我想“嗯,让我们把它们放在线程中,这样它会跑得更快”。

为了避免修改对象以扩展 TThread 并覆盖执行,我会查看如何使用过程而不是从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 并覆盖执行方法?

有趣的是这个运行得很好。

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

3 个答案:

答案 0 :(得分:0)

  

使用一个对象可以很好地工作,但是当我尝试遍历我的对象列表时,会发生奇怪的行为。

您可能没有考虑how anonymous procedures bind to variables。特别是:

  

请注意,变量捕获捕获变量 - 而非如果变量的值在通过构造匿名方法捕获后发生更改,则捕获的匿名方法变量的值也会发生变化,因为它们是具有相同存储的相同变量。捕获的变量存储在堆上,而不是堆栈中。

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

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

你实际上会在线程中引起EListError异常(至少当我测试它时 - 我不知道它为什么会发生。通过在调用{之前为线程分配OnTerminate处理程序来验证{ {1}},然后让该处理程序检查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()的实际引用隔离为局部变量的单独过程,可以消除捕获多个对象的引用时发生冲突的可能性。

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

答案 1 :(得分:0)

在评论中阅读<{3}} 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;

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

答案 2 :(得分: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;