Delphi中的線程類
猛禽[Mental Studio]
http://mental.mentsu.com
之五(大結局)
回到前面CheckSynchronize,見下面的代碼:
function CheckSynchronize(Timeout: Integer = 0): Boolean;
var
SyncPRoc: PSyncProc;
LocalSyncList: TList;
begin
if GetCurrentThreadID <> MainThreadID then
raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);
if Timeout > 0 then
WaitForSyncEvent(Timeout)
else
ResetSyncEvent;
LocalSyncList := nil;
EnterCriticalSection(ThreadLock);
try
Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));
try
Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0);
if Result then
begin
while LocalSyncList.Count > 0 do
begin
SyncProc := LocalSyncList[0];
LocalSyncList.Delete(0);
LeaveCriticalSection(ThreadLock);
try
try
SyncProc.SyncRec.FMethod;
except
SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;
end;
finally
EnterCriticalSection(ThreadLock);
end;
SetEvent(SyncProc.signal);
end;
end;
finally
LocalSyncList.Free;
end;
finally
LeaveCriticalSection(ThreadLock);
end;
end;
首先,這個方法必須在主線程中被調用(如前面通過消息傳遞到主線程),否則就拋出異常。
接下來調用ResetSyncEvent(它與前面SetSyncEvent對應的,之所以不考慮WaitForSyncEvent的情況,是因為只有在linux版下才會調用帶參數的CheckSynchronize,Windows版下都是調用默認參數0的CheckSynchronize)。
現在可以看出SyncList的用途了:它是用于記錄所有未被執行的同步方法的。因為主線程只有一個,而子線程可能有很多個,當多個子線程同時調用同步方法時,主線程可能一時無法處理,所以需要一個列表來記錄它們。
在這里用一個局部變量LocalSyncList來交換SyncList,這里用的也是一個原語:InterlockedExchange。同樣,這里也是用臨界區將對SyncList的訪問保護起來。
只要LocalSyncList不為空,則通過一個循環來依次處理累積的所有同步方法調用。最后把處理完的LocalSyncList釋放掉,退出臨界區。
再來看對同步方法的處理:首先是從列表中移出(取出并從列表中刪除)第一個同步方法調用數據。然后退出臨界區(原因當然也是為了防止死鎖)。
接著就是真正的調用同步方法了。
如果同步方法中出現異常,將被捕獲后存入同步方法數據記錄中。
重新進入臨界區后,調用SetEvent通知調用線程,同步方法執行完成了(詳見前面Synchronize中的WaitForSingleObject調用)。
至此,整個Synchronize的實現介紹完成。
最后來說一下WaitFor,它的功能就是等待線程執行結束。其代碼如下:
function TThread.WaitFor: LongWord;
var
H: array[0..1] of THandle;
WaitResult: Cardinal;
Msg: TMsg;
begin
H[0] := FHandle;
if GetCurrentThreadID = MainThreadID then
begin
WaitResult := 0;
H[1] := SyncEvent;
repeat
{ This prevents a potential deadlock if the background thread
does a SendMessage to the foreground thread }
if WaitResult = WAIT_OBJECT_0 + 2 then
PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);
CheckThreadError(WaitResult <> WAIT_FAILED);
if WaitResult = WAIT_OBJECT_0 + 1 then
CheckSynchronize;
until WaitResult = WAIT_OBJECT_0;
end else WaitForSingleObject(H[0], INFINITE);
CheckThreadError(GetExitCodeThread(H[0], Result));
end;
如果不是在主線程中執行WaitFor的話,很簡單,只要調用WaitForSingleObject等待此線程的Handle為Signaled狀態即可。
如果是在主線程中執行WaitFor則比較麻煩。首先要在Handle數組中增加一個SyncEvent,然后循環等待,直到線程結束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,詳見MSDN中關于此API的說明)。
在循環等待中作如下處理:如果有消息發生,則通過PeekMessage取出此消息(但并不把它從消息循環中移除),然后調用MsgWaitForMultipleObjects來等待線程Handle或SyncEvent出現Signaled狀態,同時監聽消息(QS_SENDMESSAGE參數,詳見MSDN中關于此API的說明)??梢园汛?/SPAN>API當作一個可以同時等待多個Handle的WaitForSingleObject。如果是SyncEvent被SetEvent(返回WAIT_OBJECT_0 + 1),則調用CheckSynchronize處理同步方法。
為什么在主線程中調用WaitFor必須用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待線程結束呢?因為防止死鎖。由于在線程函數Execute中可能調用Synchronize處理同步方法,而同步方法是在主線程中執行的,如果用WaitForSingleObject等待的話,則主線程在這里被掛起,同步方法無法執行,導致線程也被掛起,于是發生死鎖。
而改用WaitForMultipleObjects則沒有這個問題。首先,它的第三個參數為False,表示只要線程Handle或SyncEvent中只要有一個Signaled即可使主線程被喚醒,至于加上QS_SENDMESSAGE是因為Synchronize是通過消息傳到主線程來的,所以還要防止消息被阻塞。這樣,當線程中調用Synchronize時,主線程就會被喚醒并處理同步調用,在調用完成后繼續進入掛起等待狀態,直到線程結束。
至此,對線程類TThread的分析可以告一個段落了,對前面的分析作一個總結:
1、 線程類的線程必須按正常的方式結束,即Execute執行結束,所以在其中的代碼中必須在適當的地方加入足夠多的對Terminated標志的判斷,并及時退出。如果必須要“立即”退出,則不能使用線程類,而要改用API或RTL函數。
2、 對可視VCL的訪問要放在Synchronize中,通過消息傳遞到主線程中,由主線程處理。
3、 線程共享數據的訪問應該用臨界區進行保護(當然用Synchronize也行)。
4、 線程通信可以采用Event進行(當然也可以用Suspend/Resume)。
5、 當在多線程應用中使用多種線程同步方式時,一定要小心防止出現死鎖。
6、 等待線程結束要用WaitFor方法。
Dec.01-03
(終于續完了)
新聞熱點
疑難解答
圖片精選