【轉】DELPHI 線程類

原文地址:http://yyimen.blog.163.com/blog/static/179784047201211811178223/編程

Delphi中有一個線程類TThread是用來實現多線程編程的,這個絕大多數Delphi書藉都有說到,但基本上都是對TThread類的幾個成員做一簡單介紹,再說明一下Execute的實現和Synchronize的用法就完了。然而這並非多線程編程的所有,我寫此文的目的在於對此做一個補充。
    線程本質上是進程中一段併發運行的代碼。一個進程至少有一個線程,即所謂的主線程。同時還能夠有多個子線程。當一個進程中用到超過一個線程時,就是所謂的「多線程」。
    那麼這個所謂的「一段代碼」是如何定義的呢?其實就是一個函數或過程(對Delphi而言)。
    若是用Windows API來建立線程的話,是經過一個叫作CreateThread的API函數來實現的,它的定義爲:windows

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes, 
    DWORD dwStackSize, 
    LPTHREAD_START_ROUTINE lpStartAddress, 
    LPVOID lpParameter, 
    DWORD dwCreationFlags, 
    LPDWORD lpThreadId 
   );
 

    其各參數如它們的名稱所說,分別是:線程屬性(用於在NT下進行線程的安全屬性設置,在9X下無效),堆棧大小,起始地址,參數,建立標誌(用於設置線程建立時的狀態),線程ID,最後返回線程Handle。其中的起始地址就是線程函數的入口,直至線程函數結束,線程也就結束了。
    整個線程的執行過程以下圖所示:

    由於CreateThread參數不少,並且是Windows的API,因此在C Runtime Library裏提供了一個通用的線程函數(理論上能夠在任何支持線程的OS中使用):
    unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);

    Delphi也提供了一個相同功能的相似函數:
    function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord): Integer;

    這三個函數的功能是基本相同的,它們都是將線程函數中的代碼放到一個獨立的線程中執行。線程函數與通常函數的最大不一樣在於,線程函數一啓動,這三個線程啓動函數就返回了,主線程繼續向下執行,而線程函數在一個獨立的線程中執行,它要執行多久,何時返回,主線程是無論也不知道的。
    正常狀況下,線程函數返回後,線程就終止了。但也有其它方式:

Windows API:
VOID ExitThread( DWORD dwExitCode );
C Runtime Library:
void _endthread(void);
Delphi Runtime Library:
procedure EndThread(ExitCode: Integer);

    爲了記錄一些必要的線程數據(狀態/屬性等),OS會爲線程建立一個內部Object,如在Windows中那個Handle即是這個內部Object的Handle,因此在線程結束的時候還應該釋放這個Object。

    雖說用API或RTL(Runtime Library)已經能夠很方便地進行多線程編程了,可是仍是須要進行較多的細節處理,爲此Delphi在Classes單元中對線程做了一個較好的封裝,這就是VCL的線程類:TThread
    使用這個類也很簡單,大多數的Delphi書籍都有說,基本用法是:先從TThread派生一個本身的線程類(由於TThread是一個抽象類,不能生成實例),而後是Override抽象方法:Execute(這就是線程函數,也就是在線程中執行的代碼部分),若是須要用到可視VCL對象,還須要經過Synchronize過程進行。關於之方面的具體細節,這裏再也不贅述,請參考相關書籍。
    本文接下來要討論的是TThread類是如何對線程進行封裝的,也就是深刻研究一下TThread類的實現。由於只是真正地瞭解了它,才更好地使用它。
    下面是DELPHI7中TThread類的聲明(本文只討論在Windows平臺下的實現,因此去掉了全部有關Linux平臺部分的代碼):數組

 TThread = class

  private
    FHandle: THandle;
    FThreadID: THandle;
    FCreateSuspended: Boolean;
    FTerminated: Boolean;
    FSuspended: Boolean;
    FFreeOnTerminate: Boolean;
    FFinished: Boolean;
    FReturnValue: Integer;
    FOnTerminate: TNotifyEvent;
    FSynchronize: TSynchronizeRecord;
    FFatalException: TObject;
    procedure CallOnTerminate;
    class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;
    function GetPriority: TThreadPriority;
    procedure SetPriority(Value: TThreadPriority);
    procedure SetSuspended(Value: Boolean);

  protected
    procedure CheckThreadError(ErrCode: Integer); overload;
    procedure CheckThreadError(Success: Boolean); overload;
    procedure DoTerminate; virtual;
    procedure Execute; virtual; abstract;
    procedure Synchronize(Method: TThreadMethod); overload;
    property ReturnValue: Integer read FReturnValue write FReturnValue;
    property Terminated: Boolean read FTerminated;

  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure Resume;
    procedure Suspend;
    procedure Terminate;
    function WaitFor: LongWord;
    class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
    class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
    property FatalException: TObject read FFatalException;
    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
    property Handle: THandle read FHandle;
    property Priority: TThreadPriority read GetPriority write SetPriority;
    property Suspended: Boolean read FSuspended write SetSuspended;
    property ThreadID: THandle read FThreadID;
    property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
  end;

  TThread類在Delphi的RTL裏算是比較簡單的類,類成員也很少,類屬性都很簡單明白,本文將只對幾個比較重要的類成員方法和惟一的事件:OnTerminate做詳細分析。安全

    首先就是構造函數:多線程

constructor TThread.Create(CreateSuspended: Boolean);
begin
  inherited Create;
  AddThread;
  FSuspended := CreateSuspended;
  FCreateSuspended := CreateSuspended;
  FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
  if FHandle = 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);

end;

    雖然這個構造函數沒有多少代碼,但卻能夠算是最重要的一個成員,由於線程就是在這裏被建立的。
在經過Inherited調用TObject.Create後,第一句就是調用一個過程:AddThread,其源碼以下:併發

procedure AddThread;
begin
  InterlockedIncrement(ThreadCount);
end;

    //一樣有一個對應的RemoveThread:
procedure RemoveThread;
begin
  InterlockedDecrement(ThreadCount);
end;

    它們的功能很簡單,就是經過增減一個全局變量來統計進程中的線程數。只是這裏用於增減變量的並非經常使用的Inc/Dec過程,而是用了InterlockedIncrement/InterlockedDecrement這一對過程,它們實現的功能徹底同樣,都是對變量加一或減一。但它們有一個最大的區別,那就是InterlockedIncrement/InterlockedDecrement是線程安全的。即它們在多線程下能保證執行結果正確,而Inc/Dec不能。或者按操做系統理論中的術語來講,這是一對「原語」操做。
    以加一爲例來講明兩者實現細節上的不一樣:
    通常來講,對內存數據加一的操做分解之後有三個步驟:
    一、從內存中讀出數據
    二、數據加一
    三、存入內存
    如今假設在一個兩個線程的應用中用Inc進行加一操做可能出現的一種狀況:
    一、線程A從內存中讀出數據(假設爲3)  
    二、線程B從內存中讀出數據(也是3)
    三、線程A對數據加一(如今是4)
    四、線程B對數據加一(如今也是4)
    五、線程A將數據存入內存(如今內存中的數據是4)
    六、線程B也將數據存入內存(如今內存中的數據仍是4,但兩個線程都對它加了一,應該是5纔對,因此這裏出現了錯誤的結果)
    而用InterlockIncrement過程則沒有這個問題,由於所謂「原語」是一種不可中斷的操做,即操做系統能保證在一個「原語」執行完畢前不會進行線程切換。因此在上面那個例子中,只有當線程A執行完將數據存入內存後,線程B才能夠開始從中取數並進行加一操做,這樣就保證了即便是在多線程狀況下,結果也必定會是正確的。
    前面那個例子也說明一種「線程訪問衝突」的狀況,這也就是爲何線程之間須要「同步」(Synchronize),關於這個,在後面說到同步時還會再詳細討論。
    說到同步,有一個題外話:加拿大滑鐵盧大學的教授李明曾就Synchronize一詞在「線程同步」中被譯做「同步」提出過異議,我的認爲他說的其實頗有道理。在中文中「同步」的意思是「同時發生」,而「線程同步」目的就是避免這種「同時發生」的事情。而在英文中,Synchronize的意思有兩個:一個是傳統意義上的同步(To occur at the same time),另外一個是「協調一致」(To operate in unison)。在「線程同步」中的Synchronize一詞應該是指後面一種意思,即「保證多個線程在訪問同一數據時,保持協調一致,避免出錯」。不過像這樣譯得不許的詞在IT業還有不少,既然已是約定俗成了,本文也將繼續沿用,只是在這裏說明一下,由於軟件開發是一項細緻的工做,該弄清楚的,毫不能含糊。
    扯遠了,回到TThread的構造函數上,接下來最重要就是這句了:

FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);

    這裏就用到了前面說到的Delphi RTL函數BeginThread,它有不少參數,關鍵的是第3、四兩個參數。第三個參數就是前面說到的線程函數,即在線程中執行的代碼部分。第四個參數則是傳遞給線程函數的參數,在這裏就是建立的線程對象(即Self)。其它的參數中,第五個是用於設置線程在建立後即掛起,不當即執行(啓動線程的工做是在AfterConstruction中根據CreateSuspended標誌來決定的),第六個是返回線程ID。
    如今來看TThread的核心:線程函數ThreadProc。有意思的是這個線程類的核心卻不是線程的成員,而是一個全局函數(由於BeginThread過程的參數約定只能用全局函數)。下面是它的代碼:

ide

function ThreadProc(Thread: TThread): Integer;
var
  FreeThread: Boolean;
begin
  try
    if not Thread.Terminated then
    try
      Thread.Execute;
    except
      Thread.FFatalException := AcquireExceptionObject;
    end;
  finally
    FreeThread := Thread.FFreeOnTerminate;
    Result := Thread.FReturnValue;
    Thread.DoTerminate;
    Thread.FFinished := True;
    SignalSyncEvent;
    if FreeThread then Thread.Free;
    EndThread(Result);
  end;
end;

 

    雖然也沒有多少代碼,但倒是整個TThread中最重要的部分,由於這段代碼是真正在線程中執行的代碼。下面對代碼做逐行說明:
    首先判斷線程類的Terminated標誌,若是未被標誌爲終止,則調用線程類的Execute方法執行線程代碼,由於TThread是抽象類,Execute方法是抽象方法,因此本質上是執行派生類中的Execute代碼。
    因此說,Execute就是線程類中的線程函數,全部在Execute中的代碼都須要看成線程代碼來考慮,如防止訪問衝突等。
    若是Execute發生異常,則經過AcquireExceptionObject取得異常對象,並存入線程類的FFatalException成員中。
    最後是線程結束前作的一些收尾工做。局部變量FreeThread記錄了線程類的FreeOnTerminated屬性的設置,而後將線程返回值設置爲線程類的返回值屬性的值。而後執行線程類的DoTerminate方法。
DoTerminate方法的代碼以下:

procedure TThread.DoTerminate;
begin
  if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;

    很簡單,就是經過Synchronize來調用CallOnTerminate方法,而CallOnTerminate方法的代碼以下,就是簡單地調用OnTerminate事件:

procedure TThread.CallOnTerminate;
begin
  if Assigned(FOnTerminate) then FOnTerminate(Self);
end;

    由於OnTerminate事件是在Synchronize中執行的,因此本質上它並非線程代碼,而是主線程代碼(具體見後面對Synchronize的分析)。
    執行完OnTerminate後,將線程類的FFinished標誌設置爲True。
    接下來執行SignalSyncEvent過程,其代碼以下:

procedure SignalSyncEvent;
begin
  SetEvent(SyncEvent);
end;

    也很簡單,就是設置一下一個全局Event:SyncEvent,關於Event的使用,本文將在後文詳述,而SyncEvent的用途將在WaitFor過程當中說明。
    而後根據FreeThread中保存的FreeOnTerminate設置決定是否釋放線程類,在線程類釋放時,還有一些些操做,詳見接下來的析構函數實現。
    最後調用EndThread結束線程,返回線程返回值。
    至此,線程徹底結束。
    說完構造函數,再來看析構函數:函數

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資源,下降系統性能,影響到別的線程的執行速度,因此是不經濟的,有的時候甚至可能會有問題。因此不建議這樣用。
    臨界區(CriticalSection)則是一項共享數據訪問保護的技術。它其實也是至關於一個全局的布爾變量。但對它的操做有所不一樣,它只有兩個操做:Enter和Leave,一樣能夠把它的兩個狀態看成True和False,分別表示如今是否處於臨界區中。這兩個操做也是原語,因此它能夠用於在多線程應用中保護共享數據,防止訪問衝突。
    用臨界區保護共享數據的方法很簡單:在每次要訪問共享數據以前調用Enter設置進入臨界區標誌,而後再操做數據,最後調用Leave離開臨界區。它的保護原理是這樣的:當一個線程進入臨界區後,若是此時另外一個線程也要訪問這個數據,則它會在調用Enter時,發現已經有線程進入臨界區,而後此線程就會被掛起,等待當前在臨界區的線程調用Leave離開臨界區,當另外一個線程完成操做,調用Leave離開後,此線程就會被喚醒,並設置臨界區標誌,開始操做數據,這樣就防止了訪問衝突。
    之前面那個InterlockedIncrement爲例,咱們用CriticalSection(Windows API)來實現它:

Var
  InterlockedCrit : TRTLCriticalSection;
Procedure InterlockedIncrement( var aValue : Integer );
Begin
  EnterCriticalSection( InterlockedCrit );
  Inc( aValue );
  LeaveCriticalSection( InterlockedCrit );
End;

    如今再來看前面那個例子:
    1.線程A進入臨界區(假設數據爲3)
    2.線程B進入臨界區,由於A已經在臨界區中,因此B被掛起
    3.線程A對數據加一(如今是4)
    4.線程A離開臨界區,喚醒線程B(如今內存中的數據是4)
    5.線程B被喚醒,對數據加一(如今就是5了)
    6.線程B離開臨界區,如今的數據就是正確的了。
    臨界區就是這樣保護共享數據的訪問。
    關於臨界區的使用,有一點要注意:即數據訪問時的異常狀況處理。由於若是在數據操做時發生異常,將致使Leave操做沒有被執行,結果將使本應被喚醒的線程未被喚醒,可能形成程序的沒有響應。因此通常來講,以下面這樣使用臨界區纔是正確的作法:

EnterCriticalSection
Try
   //  操做臨界區數據
Finally
  LeaveCriticalSection
End;

    最後要說明的是,Event和CriticalSection都是操做系統資源,使用前都須要建立,使用完後也一樣須要釋放。如TThread類用到的一個全局Event:SyncEvent和全局CriticalSection:TheadLock,都是在InitThreadSynchronization和DoneThreadSynchronization中進行建立和釋放的,而它們則是在Classes單元的Initialization和Finalization中被調用的。
    因爲在TThread中都是用API來操做Event和CriticalSection的,因此前面都是以API爲例,其實Delphi已經提供了對它們的封裝,在SyncObjs單元中,分別是TEvent類和TCriticalSection類。用法也與前面用API的方法相差無幾。由於TEvent的構造函數參數過多,爲了簡單起見,Delphi還提供了一個用默認參數初始化的Event類:TSimpleEvent。
    順便再介紹一下另外一個用於線程同步的類:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils單元中定義的。據我所知,這是Delphi RTL中定義的最長的一個類名,還好它有一個短的別名:TMREWSync。至於它的用處,我想光看名字就能夠知道了,我也就很少說了。
    有了前面對Event和CriticalSection的準備知識,能夠正式開始討論Synchronize和WaitFor了。
    咱們知道,Synchronize是經過將部分代碼放到主線程中執行來實現線程同步的,由於在一個進程中,只有一個主線程。先來看看Synchronize的實現:oop

procedure TThread.Synchronize(Method: TThreadMethod);
begin
  FSynchronize.FThread := Self;
  FSynchronize.FSynchronizeException := nil;
  FSynchronize.FMethod := Method;
  Synchronize(@FSynchronize);
end;

 //   其中FSynchronize是一個記錄類型:
  PSynchronizeRecord = ^TSynchronizeRecord;
  TSynchronizeRecord = record
    FThread: TObject;
    FMethod: TThreadMethod;
    FSynchronizeException: TObject;
  end;

    用於進行線程和主線程之間進行數據交換,包括傳入線程類對象,同步方法及發生的異常。
    在Synchronize中調用了它的一個重載版本,並且這個重載版本比較特別,它是一個「類方法」。所謂類方法,是一種特殊的類成員方法,它的調用並不須要建立類實例,而是像構造函數那樣,經過類名調用。之因此會用類方法來實現它,是由於爲了能夠在線程對象沒有建立時也能調用它。不過實際中是用它的另外一個重載版本(也是類方法)和另外一個類方法StaticSynchronize。下面是這個Synchronize的代碼:

post

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
var
  SyncProc: TSyncProc;
begin
  if GetCurrentThreadID = MainThreadID then
    ASyncRec.FMethod
  else
  begin
    SyncProc.Signal := CreateEvent(nil, True, False, nil);
    try
      EnterCriticalSection(ThreadLock);
      try
        if SyncList = nil then
          SyncList := TList.Create;
        SyncProc.SyncRec := ASyncRec;
        SyncList.Add(@SyncProc);
        SignalSyncEvent;
        if Assigned(WakeMainThread) then
          WakeMainThread(SyncProc.SyncRec.FThread);
        LeaveCriticalSection(ThreadLock);
        try
          WaitForSingleObject(SyncProc.Signal, INFINITE);
        finally
          EnterCriticalSection(ThreadLock);
        end;
      finally
        LeaveCriticalSection(ThreadLock);
      end;
    finally
      CloseHandle(SyncProc.Signal);
    end;
    if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;
  end;
end;

    這段代碼略多一些,不過也不算太複雜。
    首先是判斷當前線程是不是主線程,若是是,則簡單地執行同步方法後返回。
    若是不是主線程,則準備開始同步過程。
    經過局部變量SyncProc記錄線程交換數據(參數)和一個Event Handle,其記錄結構以下:

  TSyncProc = record
    SyncRec: PSynchronizeRecord;
    Signal: THandle;
  end;

    而後建立一個Event,接着進入臨界區(經過全局變量ThreadLock進行,由於同時只能有一個線程進入Synchronize狀態,因此能夠用全局變量記錄),而後就是把這個記錄數據存入SyncList這個列表中(若是這個列表不存在的話,則建立它)。可見ThreadLock這個臨界區就是爲了保護對SyncList的訪問,這一點在後面介紹CheckSynchronize時會再次看到。
    再接下就是調用SignalSyncEvent,其代碼在前面介紹TThread的構造函數時已經介紹過了,它的功能就是簡單地將SyncEvent做一個Set的操做。關於這個SyncEvent的用途,將在後面介紹WaitFor時再詳述。
    接下來就是最主要的部分了:調用WakeMainThread事件進行同步操做。WakeMainThread是一個TNotifyEvent類型的全局事件。這裏之因此要用事件進行處理,是由於Synchronize方法本質上是經過消息,將須要同步的過程放到主線程中執行,若是在一些沒有消息循環的應用中(如Console或DLL)是沒法使用的,因此要使用這個事件進行處理。
    而響應這個事件的是Application對象,下面兩個方法分別用於設置和清空WakeMainThread事件的響應(來自Forms單元):

procedure TApplication.HookSynchronizeWakeup;
begin
  Classes.WakeMainThread := WakeMainThread;
end;

procedure TApplication.UnhookSynchronizeWakeup;
begin
  Classes.WakeMainThread := nil;
end;

    上面兩個方法分別是在TApplication類的構造函數和析構函數中被調用。
    這就是在Application對象中WakeMainThread事件響應的代碼,消息就是在這裏被髮出的,它利用了一個空消息來實現:

procedure TApplication.WakeMainThread(Sender: TObject);
begin
  PostMessage(Handle, WM_NULL, 0, 0);
end;

    而這個消息的響應也是在Application對象中,見下面的代碼(刪除無關的部分):

procedure TApplication.WndProc(var Message: TMessage);
…
begin
  trywith Message do
      case Msg of
        …
        WM_NULL:
          CheckSynchronize;
        …
  except
    HandleException(Self);
  end;
end;

    其中的CheckSynchronize也是定義在Classes單元中的,因爲它比較複雜,暫時不詳細說明,只要知道它是具體處理Synchronize功能的部分就好,如今繼續分析Synchronize的代碼。

    在執行完WakeMainThread事件後,就退出臨界區,而後調用WaitForSingleObject開始等待在進入臨界區前建立的那個Event。這個Event的功能是等待這個同步方法的執行結束,關於這點,在後面分析CheckSynchronize時會再說明。
    注意在WaitForSingleObject以後又從新進入臨界區,但沒有作任何事就退出了,彷佛沒有意義,但這是必須的!
    由於臨界區的Enter和Leave必須嚴格的一一對應。那麼是否能夠改爲這樣呢:

        if Assigned(WakeMainThread) then
          WakeMainThread(SyncProc.SyncRec.FThread);
        WaitForSingleObject(SyncProc.Signal, INFINITE);
      finally
        LeaveCriticalSection(ThreadLock);
      end;

    上面的代碼和原來的代碼最大的區別在於把WaitForSingleObject也歸入臨界區的限制中了。看上去沒什麼影響,還使代碼大大簡化了,但真的能夠嗎?
    事實上是不行!
    由於咱們知道,在Enter臨界區後,若是別的線程要再進入,則會被掛起。而WaitFor方法則會掛起當前線程,直到等待別的線程SetEvent後纔會被喚醒。若是改爲上面那樣的代碼的話,若是那個SetEvent的線程也須要進入臨界區的話,死鎖(Deadlock)就發生了(關於死鎖的理論,請自行參考操做系統原理方面的資料)。
    死鎖是線程同步中最須要注意的方面之一!
    最後釋放開始時建立的Event,若是被同步的方法返回異常的話,還會在這裏再次拋出異常。
    回到前面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的分析能夠告一個段落了,對前面的分析做一個總結:
    一、線程類的線程必須按正常的方式結束,即Execute執行結束,因此在其中的代碼中必須在適當的地方加入足夠多的對Terminated標誌的判斷,並及時退出。若是必需要「當即」退出,則不能使用線程類,而要改用API或RTL函數。
    二、對可視VCL的訪問要放在Synchronize中,經過消息傳遞到主線程中,由主線程處理。
    三、線程共享數據的訪問應該用臨界區進行保護(固然用Synchronize也行)。
    四、線程通訊能夠採用Event進行(固然也能夠用Suspend/Resume)。
    五、當在多線程應用中使用多種線程同步方式時,必定要當心防止出現死鎖。
    六、等待線程結束要用WaitFor方法。

  



2003-12-11 15:37:00    
 發表評語???     

 2003-12-11 15:37:50    


 發表評語???     

Delphi的TThread類使用很方便,可是有時候咱們須要在線程類中使用消息循環,delphi沒有提供.花了兩天的事件研究了一下win32的消息系統,寫了一個線程內消息循環的測試.可是沒有具體應用過,貼出來給有這方面需求的DFW參考一下.
但願你們和我討論.

{-----------------------------------------------------------------------------
 Unit Name: uMsgThread
 Author:    xwing
 eMail :    xwing@263.net ; MSN : xwing1979@hotmail.com
 Purpose:   Thread with message Loop
 History:

 2003-6-19, add function to Send Thread Message.            ver 1.0
            use Event List and waitforsingleObject
            your can use WindowMessage or ThreadMessage
 2003-6-18, Change to create a window to Recving message
 2003-6-17, Begin.
-----------------------------------------------------------------------------}
unit uMsgThread;

interface
{$WARN SYMBOL_DEPRECATED OFF}
{$DEFINE USE_WINDOW_MESSAGE}
uses
    Classes, windows, messages, forms, sysutils;

type
    TMsgThread = class(TThread)
    private
        {$IFDEF USE_WINDOW_MESSAGE}
        FWinName    : string;
        FMSGWin     : HWND;
        {$ELSE}
        FEventList  : TList;
        FCtlSect    : TRTLCriticalSection;
        {$ENDIF}
        FException  : Exception;
        fDoLoop     : Boolean;
        FWaitHandle : THandle;
        {$IFDEF USE_WINDOW_MESSAGE}
        procedure MSGWinProc(var Message: TMessage);
        {$ELSE}
        procedure ClearSendMsgEvent;
        {$ENDIF}
        procedure SetDoLoop(const Value: Boolean);
        procedure WaitTerminate;

    protected
        Msg         :tagMSG;
        
        procedure Execute; override;
        procedure HandleException;
        procedure DoHandleException;virtual;
        //Inherited the Method to process your own Message
        procedure DoProcessMsg(var Msg:TMessage);virtual;
        //if DoLoop = true then loop this procedure
        //Your can use the method to do some work needed loop.        
        procedure DoMsgLoop;virtual;
        //Initialize Thread before begin message loop        
        procedure DoInit;virtual;
        procedure DoUnInit;virtual;

        procedure PostMsg(Msg:Cardinal;wParam:Integer;lParam:Integer);
        //When Use SendMsg method Must not use Synchronize Method in ThreadLoop !!!
        //otherwise will caurse DeadLock
        procedure SendMsg(Msg:Cardinal;wParam:Integer;lParam:Integer);
        
    public
        constructor Create(Loop:Boolean=False;ThreadName: string='');
        destructor destroy;override;
        procedure AfterConstruction;override;

        //postMessage to Quit,and Free(if FreeOnTerminater = true)
        //can call this in thread loop, don't use terminate property.
        procedure QuitThread;
        //PostMessage to Quit and Wait, only call in MAIN THREAD
        procedure QuitThreadWait;
        //just like Application.processmessage.
        procedure ProcessMessage;
        //enable thread loop, no waitfor message
        property DoLoop: Boolean read fDoLoop Write SetDoLoop;

    end;

implementation

{ TMsgThread }
{//////////////////////////////////////////////////////////////////////////////}
constructor TMsgThread.Create(Loop:Boolean;ThreadName:string);
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    if ThreadName <> '' then
        FWinName := ThreadName
    else
        FWinName := 'Thread Window';
    {$ELSE}
    FEventList := TList.Create;
    InitializeCriticalSection(fCtlSect);
    {$ENDIF}

    FWaitHandle := CreateEvent(nil, True, False, nil);

    FDoLoop := Loop;            //default disable thread loop
    inherited Create(False);    //Create thread
    FreeOnTerminate := True;    //Thread quit and free object

    //Call resume Method in Constructor Method
    Resume;
    //Wait until thread Message Loop started    
    WaitForSingleObject(FWaitHandle,INFINITE);
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.AfterConstruction;
begin
end;

{------------------------------------------------------------------------------}
destructor TMsgThread.destroy;
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    {$ELSE}
    FEventList.Free;
    DeleteCriticalSection(FCtlSect);
    {$ENDIF}
    
    inherited;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.Execute;
var
    mRet:Boolean;
    aRet:Boolean;
    {$IFNDEF USE_WINDOW_MESSAGE}
    uMsg:TMessage;
    {$ENDIF}
begin
{$IFDEF USE_WINDOW_MESSAGE}
    FMSGWin := CreateWindow('STATIC',PChar(FWinName),WS_POPUP,0,0,0,0,0,0,hInstance,nil);
    SetWindowLong(FMSGWin, GWL_WNDPROC, Longint(MakeObjectInstance(MSGWinProc)));
{$ELSE}
    PeekMessage(Msg,0,WM_USER,WM_USER,PM_NOREMOVE); //Force system alloc a msgQueue
{$ENDIF}

    //notify Conctructor can returen.
    SetEvent(FWaitHandle);
    CloseHandle(FWaitHandle);

    mRet := True;
    try
        DoInit;
        while mRet do   //Message Loop
        begin
            if fDoLoop then
            begin
                aRet := PeekMessage(Msg,0,0,0,PM_REMOVE);
                if aRet and (Msg.message <> WM_QUIT) then
                begin
                    {$IFDEF USE_WINDOW_MESSAGE}
                    TranslateMessage(Msg);
                    DispatchMessage(Msg);
                    {$ELSE}
                    uMsg.Msg := Msg.message;
                    uMsg.wParam := Msg.wParam;
                    uMsg.lParam := Msg.lParam;
                    DoProcessMsg(uMsg);
                    {$ENDIF}

                    if Msg.message = WM_QUIT then
                        mRet := False;
                end;
                {$IFNDEF USE_WINDOW_MESSAGE}
                ClearSendMsgEvent;      //Clear SendMessage Event                
                {$ENDIF}
                DoMsgLoop;
            end
            else begin
                mRet := GetMessage(Msg,0,0,0);
                if mRet then
                begin
                    {$IFDEF USE_WINDOW_MESSAGE}
                    TranslateMessage(Msg);
                    DispatchMessage(Msg);
                    {$ELSE}
                    uMsg.Msg := Msg.message;
                    uMsg.wParam := Msg.wParam;
                    uMsg.lParam := Msg.lParam;
                    DoProcessMsg(uMsg);
                    ClearSendMsgEvent;      //Clear SendMessage Event
                    {$ENDIF}
                end;
            end;
        end;
        DoUnInit;
        {$IFDEF USE_WINDOW_MESSAGE}
        DestroyWindow(FMSGWin);
        FreeObjectInstance(Pointer(GetWindowLong(FMSGWin, GWL_WNDPROC)));
        {$ENDIF}
    except
        HandleException;
    end;
end;

{------------------------------------------------------------------------------}
{$IFNDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.ClearSendMsgEvent;
var
    aEvent:PHandle;
begin
    EnterCriticalSection(FCtlSect);
    try
        if FEventList.Count <> 0 then
        begin
            aEvent := FEventList.Items[0];
            if aEvent <> nil then
            begin
                SetEvent(aEvent^);
                CloseHandle(aEvent^);
                Dispose(aEvent);
            end;
            FEventList.Delete(0);
        end;
    finally
        LeaveCriticalSection(FCtlSect);
    end;
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.HandleException;
begin
    FException := Exception(ExceptObject);  //Get Current Exception object
    try
        if not (FException is EAbort) then
            inherited Synchronize(DoHandleException);
    finally
        FException := nil;
    end;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.DoHandleException;
begin
    if FException is Exception then
        Application.ShowException(FException)
    else
        SysUtils.ShowException(FException, nil);
end;

{//////////////////////////////////////////////////////////////////////////////}
{$IFDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.MSGWinProc(var Message: TMessage);
begin
    DoProcessMsg(Message);
    with Message do
        Result:=DefWindowProc(FMSGWin,Msg,wParam,lParam);
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.DoProcessMsg(var Msg:TMessage);
begin
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.ProcessMessage;
{$IFNDEF USE_WINDOW_MESSAGE}
var
    uMsg:TMessage;
{$ENDIF}
begin
    while PeekMessage(Msg,0,0,0,PM_REMOVE) do
    if Msg.message <> WM_QUIT then
    begin
        {$IFDEF USE_WINDOW_MESSAGE}
        TranslateMessage(Msg);
        DispatchMessage(msg);
        {$ELSE}
        uMsg.Msg := Msg.message;
        uMsg.wParam := Msg.wParam;
        uMsg.lParam := Msg.lParam;
        DoProcessMsg(uMsg);
        {$ENDIF}
    end;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.DoInit;
begin
end;

procedure TMsgThread.DoUnInit;
begin
end;

procedure TMsgThread.DoMsgLoop;
begin
    Sleep(1);
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.QuitThread;
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    PostMessage(FMSGWin,WM_QUIT,0,0);
    {$ELSE}
    PostThreadMessage(ThreadID,WM_QUIT,0,0);
    {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.QuitThreadWait;
begin
    QuitThread;
    WaitTerminate;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SetDoLoop(const Value: Boolean);
begin
    if Value = fDoLoop then Exit;
    fDoLoop := Value;
    if fDoLoop then
        PostMsg(WM_USER,0,0);
end;

{------------------------------------------------------------------------------}
//Can only call this method in MAIN Thread!!
procedure TMsgThread.WaitTerminate;
var
    xStart:Cardinal;
begin
    xStart:=GetTickCount;
    try
        //EnableWindow(Application.Handle,False);
        while WaitForSingleObject(Handle, 10) = WAIT_TIMEOUT do
        begin
            Application.ProcessMessages;
            if GetTickCount > (xStart + 4000) then
            begin
                TerminateThread(Handle, 0);
                Beep;
                Break;
            end;
        end;
    finally
        //EnableWindow(Application.Handle,True);
    end;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.PostMsg(Msg: Cardinal; wParam, lParam: Integer);
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    postMessage(FMSGWin,Msg,wParam,lParam);
    {$ELSE}
    EnterCriticalSection(FCtlSect);
    try
        FEventList.Add(nil);
        PostThreadMessage(ThreadID,Msg,wParam,lParam);
    finally
        LeaveCriticalSection(FCtlSect);
    end;
    {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SendMsg(Msg: Cardinal; wParam, lParam: Integer);
{$IFNDEF USE_WINDOW_MESSAGE}
var
    aEvent:PHandle;
{$ENDIF}
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    SendMessage(FMSGWin,Msg,wParam,lParam);
    {$ELSE}
    EnterCriticalSection(FCtlSect);
    try
        New(aEvent);
        aEvent^ := CreateEvent(nil, True, False, nil);
        FEventList.Add(aEvent);
        PostThreadMessage(ThreadID,Msg,wParam,lParam);
    finally
        LeaveCriticalSection(FCtlSect);
    end;
    WaitForSingleObject(aEvent^,INFINITE);
    {$ENDIF}
end;


end. 



2003-6-22 10:56:00    
 查看評語???     

 2003-6-22 11:02:24    我參考了一下msdn,還有windows核心編程.
寫了一個類來封裝這個功能,不知道對不對.
裏面使用了兩個方法,一個使用一個隱含窗體來處理消息
還有一個是直接使用thread的消息隊列來處理,可是這個時候sendmessage沒法工做,因此我本身設想了一個方法,雖然不徹底達到了要求可是我簡單測試了一下,好像還能工做.

切換兩種工做方式要修改編譯條件
{$DEFINE USE_WINDOW_MESSAGE} 使用隱含窗體來處理消息
{-$DEFINE USE_WINDOW_MESSAGE} 使用線程消息隊列來處理消息
. 


 2003-6-22 11:02:54    還有我想要等待線程開始進行消息循環的時候create函數才返回.可是如今好像尚未這樣(用一個事件來處理).只是開始進入了threadexecute函數,線程的create就返回了.可能會出問題. 


 2003-6-23 8:55:22    經過設置 DoLoop屬性能夠設定線程是否循環(不阻塞等待消息),這樣派生類線程在循環作一些其餘事情的同時還能夠接受消息. 例如:派生類裏面循環發送緩衝區的數據,還能夠響應其餘線程發送過來的消息(如中止,啓動,退出,等等) 


 2003-8-4 10:21:18    從新修改了一下,如今用起來基本沒有問題了。

{-----------------------------------------------------------------------------
 Unit Name: uMsgThread
 Author:    xwing
 eMail :    xwing@263.net ; MSN : xwing1979@hotmail.com
 Purpose:   Thread with message Loop
 History:

 2003-7-15  Write thread class without use delphi own TThread.
 2003-6-19, add function to Send Thread Message.            ver 1.0
            use Event List and waitforsingleObject
            your can use WindowMessage or ThreadMessage
 2003-6-18, Change to create a window to Recving message
 2003-6-17, Begin.
-----------------------------------------------------------------------------}
unit uMsgThread;

interface
{$WARN SYMBOL_DEPRECATED OFF}

{$DEFINE USE_WINDOW_MESSAGE}
uses
    Classes, windows, messages, forms, sysutils;

const
    NM_EXECPROC = $8FFF;
type
    EMsgThreadErr = class(Exception);
    
    TMsgThreadMethod = procedure of object;

    TMsgThread = class
    private
        SyncWindow  : HWND;
        FMethod     : TMsgThreadMethod;
        procedure SyncWindowProc(var Message: TMessage);

    private
        m_hThread   : THandle;
        threadid    : DWORD;

        {$IFDEF USE_WINDOW_MESSAGE}
        FWinName    : string;
        FMSGWin     : HWND;
        {$ELSE}
        FEventList  : TList;
        FCtlSect    : TRTLCriticalSection;
        {$ENDIF}

        FException  : Exception;
        fDoLoop     : Boolean;
        FWaitHandle : THandle;

        {$IFDEF USE_WINDOW_MESSAGE}
        procedure MSGWinProc(var Message: TMessage);
        {$ELSE}
        procedure ClearSendMsgEvent;
        {$ENDIF}

        procedure SetDoLoop(const Value: Boolean);
        procedure Execute;

    protected
        Msg         :tagMSG;

        {$IFNDEF USE_WINDOW_MESSAGE}
        uMsg        :TMessage;
        fSendMsgComp:THandle;
        {$ENDIF}

        procedure HandleException;
        procedure DoHandleException;virtual;

        //Inherited the Method to process your own Message
        procedure DoProcessMsg(var Msg:TMessage);virtual;

        //if DoLoop = true then loop this procedure
        //Your can use the method to do some work needed loop.
        procedure DoMsgLoop;virtual;

        //Initialize Thread before begin message loop
        procedure DoInit;virtual;
        procedure DoUnInit;virtual;

        procedure PostMsg(Msg:Cardinal;wParam:Integer;lParam:Integer);
        //When Use SendMsg method Must not use Synchronize Method in ThreadLoop !!!
        //otherwise will caurse DeadLock
        function SendMsg(Msg:Cardinal;wParam:Integer;lParam:Integer):Integer;

    public
        constructor Create(Loop:Boolean=False;ThreadName: string='');
        destructor destroy;override;

        // Return TRUE if the thread exists. FALSE otherwise
        function ThreadExists: BOOL;

        procedure Synchronize(syncMethod:TMsgThreadMethod);

        function WaitFor:Longword;
        function WaitTimeOut(timeout:DWORD=4000):Longword;

        //postMessage to Quit,and Free(if FreeOnTerminater = true)
        //can call this in thread loop, don't use terminate property.
        procedure QuitThread;

        //just like Application.processmessage.
        procedure ProcessMessage;

        //enable thread loop, no waitfor message
        property DoLoop: Boolean read fDoLoop Write SetDoLoop;

    end;

implementation

function msgThdInitialThreadProc(pv:Pointer):DWORD;stdcall;
var
    obj:TMsgThread;
begin
    obj := TMsgThread(pv);
    obj.execute;
    Result := 0;
end;

{ TMsgThread }
{//////////////////////////////////////////////////////////////////////////////}
constructor TMsgThread.Create(Loop:Boolean;ThreadName:string);
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    if ThreadName <> '' then
        FWinName := ThreadName
    else
        FWinName := 'Thread Window';
    {$ELSE}
    FEventList := TList.Create;
    InitializeCriticalSection(fCtlSect);
    fSendMsgComp := CreateEvent(nil, True, False, nil);
    {$ENDIF}

    FDoLoop := Loop;            //default disable thread loop

    //Create a Window for sync method
    SyncWindow := CreateWindow('STATIC','SyncWindow',WS_POPUP,0,0,0,0,0,0,hInstance,nil);
    SetWindowLong(SyncWindow, GWL_WNDPROC, Longint(MakeObjectInstance(SyncWindowProc)));


    FWaitHandle := CreateEvent(nil, True, False, nil);
    //Create Thread
    m_hThread := CreateThread(nil,0,@msgThdInitialThreadProc,Self,0,threadid);
    if m_hThread = 0 then
        raise EMsgThreadErr.Create('不能建立線程。');
    //Wait until thread Message Loop started    
    WaitForSingleObject(FWaitHandle,INFINITE);
end;

{------------------------------------------------------------------------------}
destructor TMsgThread.destroy;
begin
    if m_hThread <> 0 then
        QuitThread;
    waitfor;

    //Free Sync Window
    DestroyWindow(SyncWindow);
    FreeObjectInstance(Pointer(GetWindowLong(SyncWindow, GWL_WNDPROC)));

    {$IFDEF USE_WINDOW_MESSAGE}
    {$ELSE}
    FEventList.Free;
    DeleteCriticalSection(FCtlSect);
    CloseHandle(fSendMsgComp);
    {$ENDIF}
    
    inherited;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.Execute;
var
    mRet:Boolean;
    aRet:Boolean;
begin
{$IFDEF USE_WINDOW_MESSAGE}
    FMSGWin := CreateWindow('STATIC',PChar(FWinName),WS_POPUP,0,0,0,0,0,0,hInstance,nil);
    SetWindowLong(FMSGWin, GWL_WNDPROC, Longint(MakeObjectInstance(MSGWinProc)));
{$ELSE}
    PeekMessage(Msg,0,WM_USER,WM_USER,PM_NOREMOVE); //Force system alloc a msgQueue
{$ENDIF}

    mRet := True;
    try
        DoInit;

        //notify Conctructor can returen.
        SetEvent(FWaitHandle);
        CloseHandle(FWaitHandle);

        while mRet do   //Message Loop
        begin
            if fDoLoop then
            begin
                aRet := PeekMessage(Msg,0,0,0,PM_REMOVE);
                if aRet and (Msg.message <> WM_QUIT) then
                begin
                    {$IFDEF USE_WINDOW_MESSAGE}
                    TranslateMessage(Msg);
                    DispatchMessage(Msg);
                    {$ELSE}
                    uMsg.Msg := Msg.message;
                    uMsg.wParam := Msg.wParam;
                    uMsg.lParam := Msg.lParam;
                    DoProcessMsg(uMsg);
                    {$ENDIF}

                    if Msg.message = WM_QUIT then
                        mRet := False;
                end;
                {$IFNDEF USE_WINDOW_MESSAGE}
                ClearSendMsgEvent;      //Clear SendMessage Event                
                {$ENDIF}
                DoMsgLoop;
            end
            else begin
                mRet := GetMessage(Msg,0,0,0);
                if mRet then
                begin
                    {$IFDEF USE_WINDOW_MESSAGE}
                    TranslateMessage(Msg);
                    DispatchMessage(Msg);
                    {$ELSE}
                    uMsg.Msg := Msg.message;
                    uMsg.wParam := Msg.wParam;
                    uMsg.lParam := Msg.lParam;
                    DoProcessMsg(uMsg);
                    ClearSendMsgEvent;      //Clear SendMessage Event
                    {$ENDIF}
                end;
            end;
        end;
        DoUnInit;
        {$IFDEF USE_WINDOW_MESSAGE}
        DestroyWindow(FMSGWin);
        FreeObjectInstance(Pointer(GetWindowLong(FMSGWin, GWL_WNDPROC)));
        {$ENDIF}
    except
        HandleException;
    end;
end;

{------------------------------------------------------------------------------}
{$IFNDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.ClearSendMsgEvent;
var
    aEvent:PHandle;
begin
    EnterCriticalSection(FCtlSect);
    try
        if FEventList.Count <> 0 then
        begin
            aEvent := FEventList.Items[0];
            if aEvent <> nil then
            begin
                SetEvent(aEvent^);
                CloseHandle(aEvent^);
                Dispose(aEvent);
                WaitForSingleObject(fSendMsgComp,INFINITE);
            end;
            FEventList.Delete(0);
        end;
    finally
        LeaveCriticalSection(FCtlSect);
    end;
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.HandleException;
begin
    FException := Exception(ExceptObject);  //Get Current Exception object
    try
        if not (FException is EAbort) then
            Synchronize(DoHandleException);
    finally
        FException := nil;
    end;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.DoHandleException;
begin
    if FException is Exception then
        Application.ShowException(FException)
    else
        SysUtils.ShowException(FException, nil);
end;

{//////////////////////////////////////////////////////////////////////////////}
{$IFDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.MSGWinProc(var Message: TMessage);
begin
    DoProcessMsg(Message);
    if Message.Msg < wm_user then
        with Message do
            Result:=DefWindowProc(FMSGWin,Msg,wParam,lParam);
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.DoProcessMsg(var Msg:TMessage);
begin

end;

{------------------------------------------------------------------------------}
procedure TMsgThread.ProcessMessage;
{$IFNDEF USE_WINDOW_MESSAGE}
var
    uMsg:TMessage;
{$ENDIF}
begin
    while PeekMessage(Msg,0,0,0,PM_REMOVE) do
    if Msg.message <> WM_QUIT then
    begin
        {$IFDEF USE_WINDOW_MESSAGE}
        TranslateMessage(Msg);
        DispatchMessage(msg);
        {$ELSE}
        uMsg.Msg := Msg.message;
        uMsg.wParam := Msg.wParam;
        uMsg.lParam := Msg.lParam;
        DoProcessMsg(uMsg);
        {$ENDIF}
    end;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.DoInit;
begin
end;

procedure TMsgThread.DoUnInit;
begin
end;

procedure TMsgThread.DoMsgLoop;
begin
    Sleep(0);
end;

{//////////////////////////////////////////////////////////////////////////////}
function TMsgThread.ThreadExists: BOOL;
begin
    if m_hThread = 0 then
        Result := false
    else
        Result := True;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.QuitThread;
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    PostMessage(FMSGWin,WM_QUIT,0,0);
    {$ELSE}
    PostThreadMessage(ThreadID,WM_QUIT,0,0);
    {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SetDoLoop(const Value: Boolean);
begin
    if Value = fDoLoop then Exit;
    fDoLoop := Value;
    if fDoLoop then
        PostMsg(WM_USER,0,0);
end;

{------------------------------------------------------------------------------}
function TMsgThread.WaitTimeOut(timeout:dword):Longword;
var
    xStart:Cardinal;
    H: THandle;
begin
    H := m_hThread;
    xStart:=GetTickCount;
    while WaitForSingleObject(h, 10) = WAIT_TIMEOUT do
    begin
        Application.ProcessMessages;
        if GetTickCount > (xStart + timeout) then
        begin
            TerminateThread(h, 0);
            Break;
        end;
    end;
    GetExitCodeThread(H, Result);    
end;

{------------------------------------------------------------------------------}
function TMsgThread.WaitFor: Longword;
var
    Msg: TMsg;
    H: THandle;
begin
    H := m_hThread;
    if GetCurrentThreadID = MainThreadID then
        while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_SENDMESSAGE) = WAIT_OBJECT_0 + 1 do
            PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE)
    else
        WaitForSingleObject(H, INFINITE);
    GetExitCodeThread(H, Result);
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.PostMsg(Msg: Cardinal; wParam, lParam: Integer);
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    postMessage(FMSGWin,Msg,wParam,lParam);
    {$ELSE}
    EnterCriticalSection(FCtlSect);
    try
        FEventList.Add(nil);
        PostThreadMessage(ThreadID,Msg,wParam,lParam);
    finally
        LeaveCriticalSection(FCtlSect);
    end;
    {$ENDIF}
end;

{------------------------------------------------------------------------------}
function TMsgThread.SendMsg(Msg: Cardinal; wParam, lParam: Integer):Integer;
{$IFNDEF USE_WINDOW_MESSAGE}
var
    aEvent:PHandle;
{$ENDIF}
begin
    {$IFDEF USE_WINDOW_MESSAGE}
    Result := SendMessage(FMSGWin,Msg,wParam,lParam);
    {$ELSE}
    EnterCriticalSection(FCtlSect);
    try
        New(aEvent);
        aEvent^ := CreateEvent(nil, True, False, nil);
        FEventList.Add(aEvent);
        PostThreadMessage(ThreadID,Msg,wParam,lParam);
    finally
        LeaveCriticalSection(FCtlSect);
    end;
    WaitForSingleObject(aEvent^,INFINITE);
    Result := uMsg.Result;
    SetEvent(fSendMsgComp);
    {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.Synchronize(syncMethod: TMsgThreadMethod);
begin
    FMethod := syncMethod;
    SendMessage(SyncWindow,NM_EXECPROC,0,Longint(Self));
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SyncWindowProc(var Message: TMessage);
begin
    case Message.Msg of
        NM_EXECPROC:
        with TMsgThread(Message.lParam) do
        begin
            Message.Result := 0;
            try
                FMethod;
            except
                raise EMsgThreadErr.Create('執行同步線程方法錯誤。');
            end;
        end;
        else
            Message.Result:=DefWindowProc(SyncWindow,Message.Msg,Message.wParam,Message.lParam);
    end;
end;

end.  
相關文章
相關標籤/搜索