2017-03-07 238 views
0

我无法按照它的工作方式。与TThread.CreateAnonymousThread的奇怪行为

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


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

为什么我问这个?我有一个已经有一个进程正常工作的对象。但是现在我发现我处于需要处理该对象列表的情况。肯定的工作一个接一个很好地进行,但在我的情况下,他们是独立的其他人之一,所以我想“嗯,让他们在线程,所以它会跑得更快”。

为了避免修改对象扩展的TThread和压倒一切的执行我仰望如何与一个过程,而不是从继承的TThread发现匿名Thread的对象来执行线程。用一个对象工作真的很棒,但是当我尝试循环访问对象列表时,发生了奇怪的行为。

这也有同样的效果。

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

+0

所有这些示例都显示**未定义行为**,因为您正在从主UI线程外部访问'TMemo'。所有的结果都是随机的,可能会导致意想不到的问题。您**必须**与主UI线程同步,例如'TThread.Synchronize()'。但即使如此,您还需要考虑[匿名程序如何绑定到变量](http://docwiki.embarcadero.com/RADStudio/en/Anonymous_Methods_in_Delphi#Anonymous_Methods_Variable_Binding)。 –

+0

所以在我的情况下,ObjectList与我的对象将工作得很好?而我遇到了问题,因为我试图用可视化组件进行调试?在这种情况下,使用TMemo的Form1 –

+0

可能不会,或者您首先不会问这个问题。 –

回答

0

作品与一个对象真正伟大的,但是当我通过我的对象列表尝试循环,怪异的行为发生。

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

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

例如,如果你做这样的事情:

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

你实际上将导致线程的EListError异常(我至少当我测试了它 - 我不知道为什么会发生。通过在调用Start()之前为线程分配OnTerminate处理程序,然后让该处理程序检查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()到一个局部变量的独立程序,您删除冲突的任何机会捕捉多个对象的引用。

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

+0

哈哈哈,谢谢没有及时看到你的答案,我做出了非常类似的解决方案。我真的需要深入深入delphi的东西,这种方法指针或匿名绑定并不那么简单。谢谢。 –

+0

“通过将调用移动到CreateAnonymousThread()到一个单独的过程”真正解决问题。 –

0

看完articleRemy 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; 

这就是解决了在主对象只是变化最小的问题。