2012-12-16 54 views
1

我有一個來自TThread的孩子。一切工作正常,但我怎麼做大量暫停或恢復我創建的線程?或者我怎樣才能暫停第二個線程(它是在Button2Click中創建的)?這裏是我的代碼的一部分:德爾福:如何處理動態創建的線程

TMyThread = class(TThread) 
private 
    source_file, destination_file: string; 
    total_size, current_size, download_item_id: integer; 
protected 
    procedure ShowResult; 
    procedure Execute; override; 
public 
end; 

var 
MyThread: TMyThread; 

begin 

procedure TMyThread.Execute; 
begin 
    //Some code for download file here, it doesn't matter 
end; 


procedure TForm1.Button1Click(Sender: TObject); 
begin 
    MyThread := TMyThread.Create(True); 
    MyThread.source_file :='http://example.com/download1.zip'; 
    MyThread.destination_file := 'c:\download1.zip'; 
    MyThread.download_item_id := 0; 
    MyThread.Priority := tpNormal; 
    MyThread.FreeOnTerminate := True; 
    MyThread.Resume; 
end; 

procedure TForm1.Button2Click(Sender: TObject); 
begin 
    MyThread := TMyThread.Create(True); 
    MyThread.source_file :='http://example.com/download2.zip'; 
    MyThread.destination_file := 'c:\download2.zip'; 
    MyThread.download_item_id := 1; 
    MyThread.Priority := tpNormal; 
    MyThread.FreeOnTerminate := True; 
    MyThread.Resume; 
end; 

end. 

也就是說,如果我創建一個線程像這樣 - 這是爲我工作:

var 
MyThread1, MyThread2: TMyThread; 
... 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
    MyThread1 := TMyThread.Create(True); 
    MyThread1.source_file :='http://example.com/download1.zip'; 
    MyThread1.destination_file := 'c:\download1.zip'; 
    MyThread1.download_item_id := 0; 
    MyThread1.Priority := tpNormal; 
    MyThread1.FreeOnTerminate := True; 
    MyThread1.Resume; 
end; 

procedure TForm1.Button2Click(Sender: TObject); 
begin 
    MyThread2 := TMyThread.Create(True); 
    MyThread2.source_file :='http://example.com/download2.zip'; 
    MyThread2.destination_file := 'c:\download2.zip'; 
    MyThread2.download_item_id := 1; 
    MyThread2.Priority := tpNormal; 
    MyThread2.FreeOnTerminate := True; 
    MyThread2.Resume; 
end; 

//Terminate all of TMyThread 
procedure TForm1.Button3Click(Sender: TObject); 
begin 
    MyThread1.Terminate; 
    MyThread2.Terminate; 
    ShowMessage('All downloads were terminated!'); 
end; 

//Terminate ONLY the second of TMyThread 
procedure TForm1.Button4Click(Sender: TObject); 
begin 
    MyThread2.Terminate; 
    ShowMessage('The second download was terminated!'); 
end; 

但如何做到這一點的一組動態創建TMyThread的(如在第一個代碼示例中)?

+3

使用['TThread.Start'](http://docwiki.embarcadero.com/Libraries/XE3/en/System.Classes.TThread.Start)代替'Resume'。不推薦使用「Resume」的警告消息必須這樣說。也不要訪問班級的「私人」成員,這就是爲什麼他們是私人的。而且,不要試圖用'Suspend'或其他東西來暫停*線程。如果您真的需要這樣做,請使用系統對象,例如事件,並等待它,例如通過使用'WaitForSingleObject'。 – TLama

+0

感謝您的意見,但問題是:如何訪問動態創建的線程?我的意思是,它可能是一個大規模的'TThread.Terminate',不一定完全是'TThread.Suspend'或'TThread.Resume'。 –

+1

請參閱[我應該用什麼德爾福代碼替換我的調用不推薦TThread方法掛起?](http://stackoverflow.com/q/2097316/576719)。事件是一個可以等待的對象。如果你的線程有一個全局事件,他們都可以等待。如果你需要分別控制你的線程,在一個自定義的'TThread.Create'中傳遞一個單獨的事件對象。 –

回答

5

限制

你真的,真的不應該保持對設置爲終止時釋放的線程的引用。它要求各種各樣的問題。下面的代碼使用FreeOnTerminate設置爲True的線程。這是唯一安全的,因爲:1)當線程終止並在釋放之前,引用被刪除; 2)OnTerminate處理程序在主線程和/或線程安全TList後代的上下文中調用;和 - 最重要的是 - 3)引用僅用於從主線程的上下文中選擇性地取消線程。

只要你想使用其他任何引用(如暫停和恢復)或者可以在主線程以外的任何線程的上下文中訪問它們,你應該深入研究各種機制線程化程序執行。

幾個關鍵字:線程同步,事件信號,互斥量,信號量,關鍵部分。

一個很好的參考,以多線程編程與德爾福Multithreading - The Delphi Way

回答

如果你真的需要引用以後的線程,你需要在一些地方保存你的介紹人。但是您不需要爲每個創建的線程分別創建變量。有很多選擇。數組想到的,但可能是最簡單的是對象列表(從Contnrs單位):

TForm1 = class(TForm) 
    private 
    MyThreads: TObjectList; 

constructor TForm1.Create(AOwner: TComponent); 
begin 
    inherited; 
    MyThreads := TObjectList.Create; 
end; 

destructor TForm1.Destroy; 
begin 
    MyThreads.Free; 
    inherited; 
end; 

TObjectList有OwnsObjects屬性默認情況下爲True。這意味着釋放對象列表還將釋放它所包含的實例。這意味着你需要確保列表只包含有效的引用。通常情況下這不是問題,但是需要特別注意線程,尤其是當您使用FreeOnTerminate設置爲True時。

創建線程就像你在你的第一個例子做了一些改動:

使用局部變量(你應該避免使用全局變量,即使它們只在單元內可見儘可能) 。

將線程添加到您的對象列表中。

設置一個OnTerminate處理程序,當您還想將'FreeOnTerminate`設置爲True時從對象列表中刪除實例。

procedure TForm1.Button1Click(Sender: TObject); 
var 
    Thread: TThread;          // Local var 
begin 
    Thread := TMyThread.Create(True); 
    MyThreads.Add(Thread);         // Add to list 
    Thread.source_file :='http://example.com/download1.zip'; 
    Thread.destination_file := 'c:\download1.zip'; 
    Thread.download_item_id := 0; 
    Thread.Priority := tpNormal; 
    Thread.OnTerminate := HandleThreadTerminate;   // Ensure you keep list valid 
    Thread.FreeOnTerminate := True;      // Advice against this! 
    Thread.Start; 
end; 

procedure TForm1.HandleThreadTerminate(Sender: TObject); 
var 
    idx: Integer; 
begin 
    // Acquire Lock to protect list access from two threads 
    idx := MyThreads.IndexOf(Sender); 
    if idx > -1 then 
    MyThreads.Delete(idx); 
    // Release lock 
end; 

的HandleThreadTerminate程序應該使用一些類型的鎖,所以兩個線程不能在同一時間嘗試從列表作爲的IndexOf之間的線程切換刪除實例,並刪除可能意味着刪除錯誤的實例。處理程序中的代碼可以寫爲MyThreads.Remove(Sender)。雖然這是一個單一的聲明,但它不是線程安全的,因爲它與我在後臺顯示的代碼一樣。

編輯:實際上,正如@LURD所提到的,它恰好是線程安全的,因爲OnTerminate在主線程的上下文中被調用。我正在離開TThreadList示例,因爲如果您需要/碰巧從多個線程訪問列表,它是一種很好的方法。

要讓鎖定的東西自動處理,您可以使用TThreadList(來自classes單位)。必需的代碼更改:

當然,您需要實例化TThreadList而不是TObjectList。

constructor TForm1.Create(AOwner: TComponent); 
begin 
    inherited; 
    MyThreads := TThreadList.Create; 
end; 

因爲TThreadList沒有的OwnsObjects概念,你必須自己釋放任何剩餘線程:

destructor TForm1.Destroy; 
var 
    LockedList: TList; 
    idx: integer; 
begin 
    LockedList := MyThreads.LockList; 
    try 
    for idx := LockedList.Count - 1 downto 0 do 
     TObject(MyThreads.Items(idx)).Free; 
    finally 
    MyThreads.UnlockList; 
    end; 

    MyThreads.Free; 
    inherited; 
end; 

的OnTerminate處理器現在可以安全地被簡化爲TThreadList將採取必要的鎖定護理。

procedure TForm1.HandleThreadTerminate(Sender: TObject); 
begin 
    MyThreads.Remove(idx); 
end; 

編輯:作爲@mghie提到,釋放一個線程時FreeOnTerminate是真實的衝突與免費終止的整體思路。您可以將FreeOnTerminate設置爲false,然後釋放線程(可以終止它),或者如果要保留FreeOnTerminate處理,則應斷開OnTerminate處理程序,然後使用Terminate通知線程終止。

destructor TForm1.Destroy; 
var 
    LockedList: TList; 
    idx: integer; 
begin 
    LockedList := MyThreads.LockList; 
    try 
    for idx := LockedList.Count - 1 downto 0 do 
    begin 
     TThread(MyThreads.Items(idx)).OnTerminate := nil; 
     TThread(MyThreads.Items(idx)).Terminate; 
    end; 
    finally 
    MyThreads.UnlockList; 
    end; 

    MyThreads.Free; 
    inherited; 
end; 
+0

'OnTerminate'事件在主線程的上下文中執行。無需保護列表。 –

+1

切勿在設置了FreeOnTerminate的線程上調用Free。沒有進一步的同步就無法安全地完成,這與「FreeOnTerminate」的整個想法相沖突。 – mghie

+0

@LURD:D'oh。確實。增加了關於這個的一個筆記 –

1

答案非常簡單 - 如果你創建一個FreeOnTerminate類型的TThread那麼你不利用所有到的TThread實例任何外部引用。這樣做就像玩俄羅斯輪盤賭......它遲早會在你的臉上炸開。是的,有一些方法可以做到,但你不應該這樣做。

你可以做的是在父母和孩子之間引入一個共享通信媒介。從子線程傳遞狀態更新有許多不同的方式,不涉及任何直接引用TThread實例。 (檢查自定義窗口消息,可能是最常用的方法。)

一個非常明顯的(我認爲),簡單的例子。我在這個消息框內編碼,所以不要期望它編譯......這只是爲了幫助證明需要配置一個單獨的機制,以便您可以安全地在父母和孩子之間進行通信而不依賴於參考一些TThread實例。 (這是假設一個子線程...是的,有這樣做的其他許多方面。)

unit MyPubComm.pas; 

initialization 
uses ...; 

type 
    TThreadComm = class 
    private 
    fThreadIsBusy:Boolean; //more likely a State of some sort 
    fThreadMessage:String; 
    public 
    property ThreadIsBusy:Boolean read fThreadIsBusy write fThreadIsBusy; 
    property ThreadMessage:String read fThreadMessage write fThreadMessage; 
    end; 

    function IsChildBusy:Boolean; 
    function ChildMessage:String; 
    procedure SetThreadMessage(const MessageText:String); 

var 
    pubThreadStatus:TThreadComm; 


implementation 

function IsChildBusy:Boolean; 
begin 
    pubThreadStatus.Monitor.Enter; 
    try 
    Result := pubThreadStatus.ThreadIsBusy; 
    finally 
    pubThreadStatus.Monitor.Exit; 
    end; 
end; 

function ChildMessage:String; 
begin 
    pubThreadStatus.Monitor.Enter; 
    try 
    Result := pubThreadStatus.ThreadMessage; 
    finally 
    pubThreadStatus.Monitor.Exit; 
    end; 
end; 

procedure SetThreadMessage(const MessageText:String); 
begin 
    pubThreadStatus.Monitor.Enter; 
    try 
    pubThreadStatus.ThreadMessage := MessageText; 
    finally 
    pubThreadStatus.Monitor.Exit; 
    end; 
end; 


initialization 
    pubThreadStatus := TThreadCom.Create; 
finalization 
    pubThreadStatus.Free(); 

-------- 
unit MainAppCode.pas 
... 
implementation 
Uses MyPubComm; 

procedure TMyForm1.WorkingOnSomething(); 
begin 
    if IsChildBusy() then //safely check child state 
    begin 
     label1.caption := 'Child thread busy...'; 
    end 
    else 
    begin 
     label1.caption := ChildMessage(); //safely retrieve shared data 
    end; 
end; 
------ 
MyThread.pas 
... 
implementation 
Uses MyPubComm; 

function TMyThread.WorkerBee(); 
begin 
    If TimeToCommunicateThreadMessage then 
    begin 
    SetThreadMessage(MyMessage); //safely update shared data 
    end; 
end; 

這也應該顯示在WorkingOnSomething()檢查IsChildBusy的時間之間(一個明顯的問題)和ChildMessage()調用,實際繁忙狀態和消息文本可能會改變,這就是爲什麼需要更健壯的通信方法。