回到前面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的說明)。可以把此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方法。