Delphi中的線程類(5,大結局)

 

回到前面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版下才會調用帶參數的CheckSynchronizeWindows版下都是調用默認參數0CheckSynchronize)。

現在可以看出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等待此線程的HandleSignaled狀態即可。

如果是在主線程中執行WaitFor則比較麻煩。首先要在Handle數組中增加一個SyncEvent,然後循環等待,直到線程結束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,詳見MSDN中關於此API的說明)。

在循環等待中作如下處理:如果有消息發生,則通過PeekMessage取出此消息(但並不把它從消息循環中移除),然後調用MsgWaitForMultipleObjects來等待線程HandleSyncEvent出現Signaled狀態,同時監聽消息(QS_SENDMESSAGE參數,詳見MSDN中關於此API的說明)。可以把此API當作一個可以同時等待多個HandleWaitForSingleObject。如果是SyncEventSetEvent(返回WAIT_OBJECT_0 + 1),則調用CheckSynchronize處理同步方法。

爲什麼在主線程中調用WaitFor必須用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待線程結束呢?因爲防止死鎖。由於在線程函數Execute中可能調用Synchronize處理同步方法,而同步方法是在主線程中執行的,如果用WaitForSingleObject等待的話,則主線程在這裏被掛起,同步方法無法執行,導致線程也被掛起,於是發生死鎖。

而改用WaitForMultipleObjects則沒有這個問題。首先,它的第三個參數爲False,表示只要線程HandleSyncEvent中只要有一個Signaled即可使主線程被喚醒,至於加上QS_SENDMESSAGE是因爲Synchronize是通過消息傳到主線程來的,所以還要防止消息被阻塞。這樣,當線程中調用Synchronize時,主線程就會被喚醒並處理同步調用,在調用完成後繼續進入掛起等待狀態,直到線程結束。

 

至此,對線程類TThread的分析可以告一個段落了,對前面的分析作一個總結:

1、  線程類的線程必須按正常的方式結束,即Execute執行結束,所以在其中的代碼中必須在適當的地方加入足夠多的對Terminated標誌的判斷,並及時退出。如果必須要“立即”退出,則不能使用線程類,而要改用APIRTL函數。

2、  對可視VCL的訪問要放在Synchronize中,通過消息傳遞到主線程中,由主線程處理。

3、  線程共享數據的訪問應該用臨界區進行保護(當然用Synchronize也行)。

4、  線程通信可以採用Event進行(當然也可以用Suspend/Resume)。

5、  當在多線程應用中使用多種線程同步方式時,一定要小心防止出現死鎖。

6、  等待線程結束要用WaitFor方法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章