在線程對象被釋放之前,首先要檢查線程是否還在執行中,如果線程還在執行中(線程ID不為0,並且線程結束標志未設置),則調用Terminate 過程結束線程。Terminate 過程只是簡單地設置線程類的 Terminated標志,如下面的代碼:
procedure TThread.Terminate; begin FTerminated:= True; end;
所以線程仍然必須繼續執行到正常結束之后才行,而不是立即終止線程,這一點要注意
在這里說一些題外話:如何才能“立即”終止線程(當前是指用TThread 創建的線程)。結果當然是不行!終止線程的唯一的方法就是讓Execute 方法執行完畢,所以一般來說,要讓你的線程能盡快終止,必須在Execute 方法中在較短的時間內不斷檢查Terminated標志,以便能及時地退出。這是設計線程代碼的一個很重要的原則!
當然如果你一定要能“立即”退出線程,那么TThread 類不是一個好的選擇,因為如果用API強制終止線程的話,最終會導致TThread 線程對象不能被正確釋放,在對象析構時出現 Access Violation。這種情況你只能使用API或者RTL函數來創建線程
比如這樣一個例子:如何終結一個定時執行的線程
有一個線程( Execute 方法)是定時執行的,就像上面的紅字中提到的原則:必須在Execute 方法中在較短的時間內不斷檢查Terminated標志,以便能及時地退出
procedure TDoSomeThingThread.Execute; var i: Integer; begin inherited; CanFree := False; try while not Self.Terminated do begin ........ { 在這里編寫該線程具體所要做的事情的代碼邏輯, 如果這里面還是要循環執行一些任務, 最好能在每次循環的時候也都再去檢查一下Terminated的值 如果是True就結束循環, 就是保證在較短的事件內不斷檢查 Terminated標志,保證能夠及時退出 } ........ Sleep(5); Application.ProcessMessages; end; finally CanFree := True; {在這里將線程申請的資源進行釋放……后續操作} end; end;
注意這里面的 CanFree 是TDoSomeThingThread的一個屬性(對應:private FCanFree: Boolean;)
CanFree 是為了標記TDoSomeThingThread線程什么時候可以被釋放,在線程的Execute函數里面,會在線程跳出循環后將CanFree賦值為True(將CanFree設置為True其實也就是線程結束執行前需要進行的最后一步操作)
情況一:就是線程檢查Terminated為True,就跳出定時執行的循環,在finally里面將 CanFree設置為True
情況二:在try里面出現異常,也會跳出定時執行的循環,在finally里面將 CanFree設置為True
下面再介紹一個函數,用來結束並釋放 TDoSomeThingThread的實例
procedure DestroyAThread(testThread: TDoSomeThingThread); stdcall; var itime: Cardinal; begin if Assigned(testThread) then //首先需要判斷線程是否存在 begin testThread.Terminate; //在這里將testThread的 Terminated屬性設置為True itime := GetTickCount; //getTickCount。返回的值是自系統啟動以來所經歷的時間,單位是:毫秒 while GetTickCount - itime < 10000 do begin Sleep(1); //Sleep()函數的參數的單位也是毫秒 if testThread.CanFree then begin Break; end; end; testThread.Free(); //這里顯示使用Free方法釋放線程的資源 end; end;
在這里上面的代碼(線程的 Execute和 函數DestoryAThread)
1) testThread正在執行
就是正在執行Execute 里面的代碼,這時候在Execute 里面檢查 Terminated的值,因為是Fasle,所以就可以先進入 第一個 while循環,在進入 for循環,檢查Terminated還是為 False,所以繼續執行
2)調用DestroyAThread(testThread);
首先檢查testThread是不是 Assigned,如果是,就用 testThread.Terminate; 將它的Terminated 屬性設置為True,然后開始進入循環等待 testThread的Execute里面是不是檢查到了 Terminated被設置為True了
其中的循環代碼段
while GetTickCount - itime < 10000 do begin Sleep(1); if testThread.CanFree then begin Break; end; end;
在10毫秒鍾之內每過一秒去檢查一下 testThread的CanFree的值,如果是True就break跳出循環。當線程的Execute執行函數中檢測到DestroyAThread(testThread); 函數使用線程的Terminate 方法將線程的Terminated 屬性設置為True,這時候線程就會開始跳出原來的循環,跳出循環后會將CanFree 屬性設置為True,因為DestroyAThread(testThread) 函數會循環檢查線程的CanFree屬性,如果檢查到其為True,那么就說明線程已經結束了循環,結束了執行,就可以調線程的Free方法來釋放線程資源了。
所以這就是一個結束線程的操作與線程本身的一個反復通信確認能不能結束線程、確認能不能釋放線程資源的過程!
另外上面 Execute的代碼中我注釋說:
如果while循環(定時循環)里面還是要循環執行一些任務, 最好能在每次循環的時候也都再去檢查一下Terminated的值 如果是True就結束循環, 就是保證在較短的事件內不斷檢查 Terminated標志,保證能夠及時退出
比如
procedure TDoSomeThingThread.Execute; var i: Integer; begin inherited; CanFree := False; try while not Self.Terminated do begin for i:=0 to 1000 do begin if Self.Terminated then break; { 執行內層循環的代碼 } end; Sleep(5); Application.ProcessMessages; end; finally CanFree := True; {在這里將線程申請的資源進行釋放……后續操作} end; end;
之所以在內部的循環中還要每次循環檢查 Terminated的值,就是為了保證能夠及時退出,因為如果不每次循環都檢查,有可能出現這樣的情況:在DestroyAThread里面,將Terminated設置為True,但是這時候線程的Execute 里面剛好執行過檢查(所以檢查到的Terminated為False,所以就會去繼續執行),這時候進入它的工作代碼中,如果是一個很大的循環,花費的時間假如說是13毫秒,超過了10毫秒,這時候就出現了問題,當線程檢查到 Terminated為True,並將CanFree設置為 True時,但是這時候已經過去了 10 毫秒了,所以在 DestroyAThread里面就沒有檢查到 CanFree,所以這時候線程控制、線程終止就出現問題了
所以通過這個也可以理解為什么要必須在Execute 方法中在較短的時間內不斷檢查Terminated標志,以便能及時地退出。
這個較短的時間怎么去設置、怎么去控制是多線程編程的一個很重要的技巧!!一方面要考慮到Execute里面怎么去保證能在較短的時間內檢查Terminated的值,還要考慮在類似DestroyAThread這樣的一個結束線程的函數中設置多少時間來等待線程的反饋
因為還說上面這個例子,還可能存在這樣的情況:加入在定時循環里面還有一層(或多層循環),而所有的循環每個循環之前也都去檢查了Terminated的值,加入就在其剛檢查不為 True的時候(當然就會去繼續下面的代碼執行),而恰好這時候DestroyAThread函數將 Terminated設置為 True,然后循環10毫秒等待 線程檢查到 Terminated的值去結束自己的任務、釋放資源,並將 CanFree標識設置為 True通知 DestroyAThread,但是出現了問題:線程的這次循環因為出現死鎖或者等待資源或者其他原因而執行了超過10毫秒(假如說20毫秒),20毫秒之后才檢查到Terminated的值,再去結束任務、釋放資源的時候,可是DestroyAThread因為已經等待超過 10毫秒了。所以這里也就出現了很令人糾結的問題。
??這個問題要怎么解決呢
3)再到testThread的 Execute方法里面
檢查到 Terminated屬性設置為True(就是通知它要去結束執行了),就跳出定時執行的循環,結束它的工作,如果線程申請了很多資源,這時候也要去釋放申請的資源,並將 CanFree設置為True,等待別人檢查到它可以退出,並將它Free掉
4)再看DestroyAThread(testThread); 里
這里循環檢查到 CanFree被 testThread 的Execute方法設置為 True(就是告訴函數,testThread已經在自己的Execute方法里面結束執行的工作了,可以被釋放了),然后就 testThread.Free;來釋放線程
注意:不要直接調用Destroy(),而調用更安全的Free()方法,因為Free()首選進行檢查保證這個對象實例不為NIL,然后調用對象的析構方法Destroy()。