2017-03-07 279 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; 

這就是解決了在主對象只是變化最小的問題。