Delphi中的執行緒類
猛禽[Mental Studio]
http://mental.mentsu.com
之三
說完構造函數,再來看析構函數:
destructor TThread.Destroy;
begin
if (FThreadID <> 0) and not FFinished then
begin
Terminate;
if FCreateSuspended then
Resume;
WaitFor;
end;
if FHandle <> 0 then CloseHandle(FHandle);
inherited Destroy;
FFatalException.Free;
RemoveThread;
end;
在線程物件被釋放之前,首先要檢查線程是否還在執行中,如果線程還在執行中(線程ID不為0,並且線程結束標誌未設定),則呼叫Terminate過程結束線程。 Terminate過程只是簡單地設定線程類別的Terminated標誌,如下面的程式碼:
PRocedure TThread.Terminate;
begin
FTerminated := True;
end;
所以線程仍然必須繼續執行到正常結束後才行,而不是立即終止線程,這一點要注意。
在這裡說一點題外話:很多人都問過我,如何才能「立即」終止線程(當然是指用TThread創建的線程)。結果當然是不行!終止執行緒的唯一方法就是讓Execute方法執行完畢,所以一般來說,要讓你的執行緒能夠盡快終止,必須在Execute方法中在較短的時間內不斷地檢查Terminated標誌,以便能及時地退出。這是設計線程程式碼的一個很重要的原則!
當然如果你一定要能「立即」退出線程,那麼TThread類別不是一個好的選擇,因為如果用API強制終止線程的話,最終會導致TThread線程物件不能被正確釋放,在物件析構時出現access Violation。這種情況你只能用API或RTL函數來建立執行緒。
如果執行緒處於啟動掛起狀態,則將執行緒轉入運行狀態,然後呼叫WaitFor進行等待,其功能就是等待到執行緒結束後才繼續向下執行。關於WaitFor的實現,將放到後面說明。
執行緒結束後,關閉執行緒Handle(正常執行緒建立的情況下Handle都是存在的),釋放作業系統所建立的執行緒物件。
然後呼叫TObject.Destroy釋放本對象,並釋放已經捕獲的異常對象,最後呼叫RemoveThread減少進程的執行緒數。
其它關於Suspend/Resume及線程優先級設定等方面,不是本文的重點,不再贅述。以下要討論的是本文的另兩個重點:Synchronize和WaitFor。
但是在介紹這兩個函數之前,需要先介紹另外兩個執行緒同步技術:事件和臨界區。
事件(Event)與Delphi中的事件有所不同。從本質上來說,Event其實相當於一個全域的布林變數。它有兩個賦值操作:Set和Reset,相當於把它設為True或False。而檢查它的值是透過WaitFor操作進行。對應在Windows平台上,是三個API函數:SetEvent、ResetEvent、WaitForSingleObject(實作WaitFor功能的API還有幾個,這是最簡單的一個)。
這三個都是原語,所以Event可以實現一般布林變數不能實現的在多執行緒中的應用。 Set和Reset的功能前面已經說過了,現在來談談WaitFor的功能:
WaitFor的功能是檢查Event的狀態是否為Set狀態(相當於True),如果是則立即傳回,如果不是,則等待它變成Set狀態,在等待期間,呼叫WaitFor的執行緒處於掛起狀態。另外WaitFor有一個參數用於超時設置,如果此參數為0,則不等待,立即返回Event的狀態,如果是INFINITE則無限等待,直到Set狀態發生,若是一個有限的數值,則等待相應的毫秒數後返回Event的狀態。
當Event從Reset狀態轉換到Set狀態時,喚醒其它由於WaitFor這個Event而掛起的線程,這就是它為什麼叫Event的原因。所謂「事件」就是指「狀態的轉換」。透過Event可以在執行緒間傳遞這種「狀態轉換」訊息。
當然用一個受保護(見下面的臨界區介紹)的布林變數也能實現類似的功能,只要用一個循環檢查此佈林值的程式碼來取代WaitFor即可。從功能上說完全沒有問題,但實際使用中就會發現,這樣的等待會佔用大量的CPU資源,降低系統效能,影響到別的執行緒的執行速度,所以是不經濟的,有的時候甚至可能會有問題。所以不建議這樣用。
(待續)