TMsgThread, TCommThread -- 在delphi线程中实现消息循环
http://delphi.cjcsoft.net//viewthread.php?tid=635
在delphi线程中实现消息循环
Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供.
{----------------------------------------------------------------------------- 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.
我参考了一下msdn,还有windows核心编程. 写了一个类来封装这个功能,不知道对不对.
里面使用了两个方法,一个使用一个隐含窗体来处理消息
还有一个是直接使用thread的消息队列来处理,但是这个时候sendmessage无法工作,
所以我自己设想了一个方法,虽然不完全达到了要求但是我简单测试了一下,好像还能工作.
切换两种工作方式要修改编译条件
{$DEFINE USE_WINDOW_MESSAGE} 使用隐含窗体来处理消息
{-$DEFINE USE_WINDOW_MESSAGE} 使用线程消息队列来处理消息
还有我想要等待线程开始进行消息循环的时候create函数才返回.
但是现在好像还没有这样(用一个事件来处理).只是开始进入了threadexecute函数,线程的create就返回了.可能会出问题.
通过设置 DoLoop属性可以设定线程是否循环(不阻塞等待消息),这样派生类线程在循环做一些其他事情的同时还可以接受消息. 例如:
派生类里面循环发送缓冲区的数据,还可以响应其他线程发送过来的消息(如停止,启动,退出,等等)
重新修改了一下,现在用起来基本没有问题了。
{ ----------------------------------------------------------------------------- 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.
I took a look at OmniThreadLibrary and it looked like overkill for my purposes.
I wrote a simple library I call TCommThread.
It allows you to pass data back to the main thread without worrying about
any of the complexities of threads or Windows messages.
Here's the code if you'd like to try it.
CommThread Library:
1 unit Threading.CommThread; 2 3 interface 4 5 uses 6 Classes, SysUtils, ExtCtrls, SyncObjs, Generics.Collections, DateUtils; 7 8 const 9 CTID_USER = 1000; 10 PRM_USER = 1000; 11 12 CTID_STATUS = 1; 13 CTID_PROGRESS = 2; 14 15 type 16 TThreadParams = class(TDictionary<String, Variant>); 17 TThreadObjects = class(TDictionary<String, TObject>); 18 19 TCommThreadParams = class(TObject) 20 private 21 FThreadParams: TThreadParams; 22 FThreadObjects: TThreadObjects; 23 public 24 constructor Create; 25 destructor Destroy; override; 26 27 procedure Clear; 28 29 function GetParam(const ParamName: String): Variant; 30 function SetParam(const ParamName: String; ParamValue: Variant): TCommThreadParams; 31 function GetObject(const ObjectName: String): TObject; 32 function SetObject(const ObjectName: String; Obj: TObject): TCommThreadParams; 33 end; 34 35 TCommQueueItem = class(TObject) 36 private 37 FSender: TObject; 38 FMessageId: Integer; 39 FCommThreadParams: TCommThreadParams; 40 public 41 destructor Destroy; override; 42 43 property Sender: TObject read FSender write FSender; 44 property MessageId: Integer read FMessageId write FMessageId; 45 property CommThreadParams: TCommThreadParams read FCommThreadParams write FCommThreadParams; 46 end; 47 48 TCommQueue = class(TQueue<TCommQueueItem>); 49 50 ICommDispatchReceiver = interface 51 ['{A4E2C9D1-E4E8-497D-A9BF-FAFE2D3A7C49}'] 52 procedure QueueMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); 53 procedure CommThreadTerminated(Sender: TObject); 54 function Cancelled: Boolean; 55 end; 56 57 TCommThread = class(TThread) 58 protected 59 FCommThreadParams: TCommThreadParams; 60 FCommDispatchReceiver: ICommDispatchReceiver; 61 FName: String; 62 FProgressFrequency: Integer; 63 FNextSendTime: TDateTime; 64 65 procedure SendStatusMessage(const StatusText: String; StatusType: Integer = 0); virtual; 66 procedure SendProgressMessage(ProgressID: Int64; Progress, ProgressMax: Integer; AlwaysSend: Boolean = TRUE); virtual; 67 public 68 constructor Create(CommDispatchReceiver: TObject); reintroduce; virtual; 69 destructor Destroy; override; 70 71 function SetParam(const ParamName: String; ParamValue: Variant): TCommThread; 72 function GetParam(const ParamName: String): Variant; 73 function SetObject(const ObjectName: String; Obj: TObject): TCommThread; 74 function GetObject(const ObjectName: String): TObject; 75 procedure SendCommMessage(MessageId: Integer; CommThreadParams: TCommThreadParams); virtual; 76 77 property Name: String read FName; 78 end; 79 80 TCommThreadClass = Class of TCommThread; 81 82 TCommThreadQueue = class(TObjectList<TCommThread>); 83 84 TCommThreadDispatchState = ( 85 ctsIdle, 86 ctsActive, 87 ctsTerminating 88 ); 89 90 TOnReceiveThreadMessage = procedure(Source, Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams) of object; 91 TOnStateChange = procedure(Sender: TObject; State: TCommThreadDispatchState) of object; 92 TOnStatus = procedure(Source, Sender: TObject; const ID: String; StatusText: String; StatusType: Integer) of object; 93 TOnProgress = procedure(Source, Sender: TObject; const ID: String; Progress, ProgressMax: Integer) of object; 94 95 TBaseCommThreadDispatch = class(TComponent, ICommDispatchReceiver) 96 private 97 FProcessQueueTimer: TTimer; 98 FCSReceiveMessage: TCriticalSection; 99 FCSCommThreads: TCriticalSection; 100 FCommQueue: TCommQueue; 101 FActiveThreads: TList; 102 FCommThreadClass: TCommThreadClass; 103 FCommThreadDispatchState: TCommThreadDispatchState; 104 105 function CreateThread(const ThreadName: String = ''): TCommThread; 106 function GetActiveThreadCount: Integer; 107 function GetStateText: String; 108 protected 109 FOnReceiveThreadMessage: TOnReceiveThreadMessage; 110 FOnStateChange: TOnStateChange; 111 FOnStatus: TOnStatus; 112 FOnProgress: TOnProgress; 113 FManualMessageQueue: Boolean; 114 FProgressFrequency: Integer; 115 116 procedure SetManualMessageQueue(const Value: Boolean); 117 procedure SetProcessQueueTimerInterval(const Value: Integer); 118 procedure SetCommThreadDispatchState(const Value: TCommThreadDispatchState); 119 procedure QueueMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); 120 procedure OnProcessQueueTimer(Sender: TObject); 121 function GetProcessQueueTimerInterval: Integer; 122 123 procedure CommThreadTerminated(Sender: TObject); virtual; 124 function Finished: Boolean; virtual; 125 126 procedure DoOnReceiveThreadMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); virtual; 127 procedure DoOnStateChange; virtual; 128 129 procedure TerminateActiveThreads; 130 131 property OnReceiveThreadMessage: TOnReceiveThreadMessage read FOnReceiveThreadMessage write FOnReceiveThreadMessage; 132 property OnStateChange: TOnStateChange read FOnStateChange write FOnStateChange; 133 property OnStatus: TOnStatus read FOnStatus write FOnStatus; 134 property OnProgress: TOnProgress read FOnProgress write FOnProgress; 135 136 property ProgressFrequency: Integer read FProgressFrequency write FProgressFrequency; 137 property ProcessQueueTimerInterval: Integer read GetProcessQueueTimerInterval write SetProcessQueueTimerInterval; 138 property ManualMessageQueue: Boolean read FManualMessageQueue write SetManualMessageQueue; 139 property CommThreadDispatchState: TCommThreadDispatchState read FCommThreadDispatchState write SetCommThreadDispatchState; 140 public 141 constructor Create(AOwner: TComponent); override; 142 destructor Destroy; override; 143 144 function NewThread(const ThreadName: String = ''): TCommThread; virtual; 145 procedure ProcessMessageQueue; virtual; 146 procedure Stop; virtual; 147 function State: TCommThreadDispatchState; 148 function Cancelled: Boolean; 149 150 property ActiveThreadCount: Integer read GetActiveThreadCount; 151 property StateText: String read GetStateText; 152 153 property CommThreadClass: TCommThreadClass read FCommThreadClass write FCommThreadClass; 154 end; 155 156 TCommThreadDispatch = class(TBaseCommThreadDispatch) 157 published 158 property OnReceiveThreadMessage: TOnReceiveThreadMessage read FOnReceiveThreadMessage write FOnReceiveThreadMessage; 159 property OnStateChange: TOnStateChange read FOnStateChange write FOnStateChange; 160 161 property ProgressFrequency: Integer read FProgressFrequency write FProgressFrequency; 162 property ProcessQueueTimerInterval: Integer read GetProcessQueueTimerInterval write SetProcessQueueTimerInterval; 163 property ManualMessageQueue: Boolean read FManualMessageQueue write SetManualMessageQueue; 164 end; 165 166 TBaseStatusCommThreadDispatch = class(TBaseCommThreadDispatch) 167 protected 168 FOnStatus: TOnStatus; 169 FOnProgress: TOnProgress; 170 171 procedure DoOnReceiveThreadMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); override; 172 173 procedure DoOnStatus(Sender: TObject;const ID: String; const StatusText: String; StatusType: Integer); virtual; 174 procedure DoOnProgress(Sender: TObject; const ID: String; Progress, ProgressMax: Integer); virtual; 175 176 property OnStatus: TOnStatus read FOnStatus write FOnStatus; 177 property OnProgress: TOnProgress read FOnProgress write FOnProgress; 178 end; 179 180 TStatusCommThreadDispatch = class(TBaseStatusCommThreadDispatch) 181 published 182 property OnReceiveThreadMessage: TOnReceiveThreadMessage read FOnReceiveThreadMessage write FOnReceiveThreadMessage; 183 property OnStateChange: TOnStateChange read FOnStateChange write FOnStateChange; 184 property OnStatus: TOnStatus read FOnStatus write FOnStatus; 185 property OnProgress: TOnProgress read FOnProgress write FOnProgress; 186 187 property ProgressFrequency: Integer read FProgressFrequency write FProgressFrequency; 188 property ProcessQueueTimerInterval: Integer read GetProcessQueueTimerInterval write SetProcessQueueTimerInterval; 189 property ManualMessageQueue: Boolean read FManualMessageQueue write SetManualMessageQueue; 190 end; 191 192 implementation 193 194 const 195 PRM_STATUS_TEXT = 'Status'; 196 PRM_STATUS_TYPE = 'Type'; 197 PRM_PROGRESS_ID = 'ProgressID'; 198 PRM_PROGRESS = 'Progess'; 199 PRM_PROGRESS_MAX = 'ProgressMax'; 200 201 resourcestring 202 StrCommReceiverMustSupportInterface = 'CommDispatchReceiver must support ICommDispatchReceiver interface'; 203 StrSenderMustBeATCommThread = 'Sender must be a TCommThread'; 204 StrUnableToFindTerminatedThread = 'Unable to find the terminated thread'; 205 StrIdle = 'Idle'; 206 StrTerminating = 'Terminating'; 207 StrActive = 'Active'; 208 209 { TCommThread } 210 211 constructor TCommThread.Create(CommDispatchReceiver: TObject); 212 begin 213 Assert(Supports(CommDispatchReceiver, ICommDispatchReceiver, FCommDispatchReceiver), StrCommReceiverMustSupportInterface); 214 215 inherited Create(TRUE); 216 217 FCommThreadParams := TCommThreadParams.Create; 218 end; 219 220 destructor TCommThread.Destroy; 221 begin 222 FCommDispatchReceiver.CommThreadTerminated(Self); 223 224 FreeAndNil(FCommThreadParams); 225 226 inherited; 227 end; 228 229 function TCommThread.GetObject(const ObjectName: String): TObject; 230 begin 231 Result := FCommThreadParams.GetObject(ObjectName); 232 end; 233 234 function TCommThread.GetParam(const ParamName: String): Variant; 235 begin 236 Result := FCommThreadParams.GetParam(ParamName); 237 end; 238 239 procedure TCommThread.SendCommMessage(MessageId: Integer; 240 CommThreadParams: TCommThreadParams); 241 begin 242 FCommDispatchReceiver.QueueMessage(Self, MessageId, CommThreadParams); 243 end; 244 245 procedure TCommThread.SendProgressMessage(ProgressID: Int64; Progress, 246 ProgressMax: Integer; AlwaysSend: Boolean); 247 begin 248 if (AlwaysSend) or (now > FNextSendTime) then 249 begin 250 // Send a status message to the comm receiver 251 SendCommMessage(CTID_PROGRESS, TCommThreadParams.Create 252 .SetParam(PRM_PROGRESS_ID, ProgressID) 253 .SetParam(PRM_PROGRESS, Progress) 254 .SetParam(PRM_PROGRESS_MAX, ProgressMax)); 255 256 if not AlwaysSend then 257 FNextSendTime := now + (FProgressFrequency * OneMillisecond); 258 end; 259 end; 260 261 procedure TCommThread.SendStatusMessage(const StatusText: String; 262 StatusType: Integer); 263 begin 264 // Send a status message to the comm receiver 265 SendCommMessage(CTID_STATUS, TCommThreadParams.Create 266 .SetParam(PRM_STATUS_TEXT, StatusText) 267 .SetParam(PRM_STATUS_TYPE, StatusType)); 268 end; 269 270 function TCommThread.SetObject(const ObjectName: String; 271 Obj: TObject): TCommThread; 272 begin 273 Result := Self; 274 275 FCommThreadParams.SetObject(ObjectName, Obj); 276 end; 277 278 function TCommThread.SetParam(const ParamName: String; 279 ParamValue: Variant): TCommThread; 280 begin 281 Result := Self; 282 283 FCommThreadParams.SetParam(ParamName, ParamValue); 284 end; 285 286 287 { TCommThreadDispatch } 288 289 function TBaseCommThreadDispatch.Cancelled: Boolean; 290 begin 291 Result := State = ctsTerminating; 292 end; 293 294 procedure TBaseCommThreadDispatch.CommThreadTerminated(Sender: TObject); 295 var 296 idx: Integer; 297 begin 298 FCSCommThreads.Enter; 299 try 300 Assert(Sender is TCommThread, StrSenderMustBeATCommThread); 301 302 // Find the thread in the active thread list 303 idx := FActiveThreads.IndexOf(Sender); 304 305 Assert(idx <> -1, StrUnableToFindTerminatedThread); 306 307 // if we find it, remove it (we should always find it) 308 FActiveThreads.Delete(idx); 309 finally 310 FCSCommThreads.Leave; 311 end; 312 end; 313 314 constructor TBaseCommThreadDispatch.Create(AOwner: TComponent); 315 begin 316 inherited; 317 318 FCommThreadClass := TCommThread; 319 320 FProcessQueueTimer := TTimer.Create(nil); 321 FProcessQueueTimer.Enabled := FALSE; 322 FProcessQueueTimer.Interval := 5; 323 FProcessQueueTimer.OnTimer := OnProcessQueueTimer; 324 FProgressFrequency := 200; 325 326 FCommQueue := TCommQueue.Create; 327 328 FActiveThreads := TList.Create; 329 330 FCSReceiveMessage := TCriticalSection.Create; 331 FCSCommThreads := TCriticalSection.Create; 332 end; 333 334 destructor TBaseCommThreadDispatch.Destroy; 335 begin 336 // Stop the queue timer 337 FProcessQueueTimer.Enabled := FALSE; 338 339 TerminateActiveThreads; 340 341 // Pump the queue while there are active threads 342 while CommThreadDispatchState <> ctsIdle do 343 begin 344 ProcessMessageQueue; 345 346 sleep(10); 347 end; 348 349 // Free everything 350 FreeAndNil(FProcessQueueTimer); 351 FreeAndNil(FCommQueue); 352 FreeAndNil(FCSReceiveMessage); 353 FreeAndNil(FCSCommThreads); 354 FreeAndNil(FActiveThreads); 355 356 inherited; 357 end; 358 359 procedure TBaseCommThreadDispatch.DoOnReceiveThreadMessage(Sender: TObject; 360 MessageId: Integer; CommThreadParams: TCommThreadParams); 361 begin 362 // Don't send the messages if we're being destroyed 363 if not (csDestroying in ComponentState) then 364 begin 365 if Assigned(FOnReceiveThreadMessage) then 366 FOnReceiveThreadMessage(Self, Sender, MessageId, CommThreadParams); 367 end; 368 end; 369 370 procedure TBaseCommThreadDispatch.DoOnStateChange; 371 begin 372 if (Assigned(FOnStateChange)) and (not (csDestroying in ComponentState)) then 373 FOnStateChange(Self, FCommThreadDispatchState); 374 end; 375 376 function TBaseCommThreadDispatch.GetActiveThreadCount: Integer; 377 begin 378 Result := FActiveThreads.Count; 379 end; 380 381 function TBaseCommThreadDispatch.GetProcessQueueTimerInterval: Integer; 382 begin 383 Result := FProcessQueueTimer.Interval; 384 end; 385 386 387 function TBaseCommThreadDispatch.GetStateText: String; 388 begin 389 case State of 390 ctsIdle: Result := StrIdle; 391 ctsTerminating: Result := StrTerminating; 392 ctsActive: Result := StrActive; 393 end; 394 end; 395 396 function TBaseCommThreadDispatch.NewThread(const ThreadName: String): TCommThread; 397 begin 398 if FCommThreadDispatchState = ctsTerminating then 399 Result := nil 400 else 401 begin 402 // Make sure we're active 403 if CommThreadDispatchState = ctsIdle then 404 CommThreadDispatchState := ctsActive; 405 406 Result := CreateThread(ThreadName); 407 408 FActiveThreads.Add(Result); 409 410 if ThreadName = '' then 411 Result.FName := IntToStr(Integer(Result)) 412 else 413 Result.FName := ThreadName; 414 415 Result.FProgressFrequency := FProgressFrequency; 416 end; 417 end; 418 419 function TBaseCommThreadDispatch.CreateThread( 420 const ThreadName: String): TCommThread; 421 begin 422 Result := FCommThreadClass.Create(Self); 423 424 Result.FreeOnTerminate := TRUE; 425 end; 426 427 procedure TBaseCommThreadDispatch.OnProcessQueueTimer(Sender: TObject); 428 begin 429 ProcessMessageQueue; 430 end; 431 432 procedure TBaseCommThreadDispatch.ProcessMessageQueue; 433 var 434 CommQueueItem: TCommQueueItem; 435 begin 436 if FCommThreadDispatchState in [ctsActive, ctsTerminating] then 437 begin 438 if FCommQueue.Count > 0 then 439 begin 440 FCSReceiveMessage.Enter; 441 try 442 CommQueueItem := FCommQueue.Dequeue; 443 444 while Assigned(CommQueueItem) do 445 begin 446 try 447 DoOnReceiveThreadMessage(CommQueueItem.Sender, CommQueueItem.MessageId, CommQueueItem.CommThreadParams); 448 finally 449 FreeAndNil(CommQueueItem); 450 end; 451 452 if FCommQueue.Count > 0 then 453 CommQueueItem := FCommQueue.Dequeue; 454 end; 455 finally 456 FCSReceiveMessage.Leave 457 end; 458 end; 459 460 if Finished then 461 begin 462 FCommThreadDispatchState := ctsIdle; 463 464 DoOnStateChange; 465 end; 466 end; 467 end; 468 469 function TBaseCommThreadDispatch.Finished: Boolean; 470 begin 471 Result := FActiveThreads.Count = 0; 472 end; 473 474 procedure TBaseCommThreadDispatch.QueueMessage(Sender: TObject; MessageId: Integer; 475 CommThreadParams: TCommThreadParams); 476 var 477 CommQueueItem: TCommQueueItem; 478 begin 479 FCSReceiveMessage.Enter; 480 try 481 CommQueueItem := TCommQueueItem.Create; 482 CommQueueItem.Sender := Sender; 483 CommQueueItem.MessageId := MessageId; 484 CommQueueItem.CommThreadParams := CommThreadParams; 485 486 FCommQueue.Enqueue(CommQueueItem); 487 finally 488 FCSReceiveMessage.Leave 489 end; 490 end; 491 492 procedure TBaseCommThreadDispatch.SetCommThreadDispatchState( 493 const Value: TCommThreadDispatchState); 494 begin 495 if FCommThreadDispatchState <> ctsTerminating then 496 begin 497 if Value = ctsActive then 498 begin 499 if not FManualMessageQueue then 500 FProcessQueueTimer.Enabled := TRUE; 501 end 502 else 503 TerminateActiveThreads; 504 end; 505 506 FCommThreadDispatchState := Value; 507 508 DoOnStateChange; 509 end; 510 511 procedure TBaseCommThreadDispatch.SetManualMessageQueue(const Value: Boolean); 512 begin 513 FManualMessageQueue := Value; 514 end; 515 516 procedure TBaseCommThreadDispatch.SetProcessQueueTimerInterval(const Value: Integer); 517 begin 518 FProcessQueueTimer.Interval := Value; 519 end; 520 521 function TBaseCommThreadDispatch.State: TCommThreadDispatchState; 522 begin 523 Result := FCommThreadDispatchState; 524 end; 525 526 procedure TBaseCommThreadDispatch.Stop; 527 begin 528 if CommThreadDispatchState = ctsActive then 529 TerminateActiveThreads; 530 end; 531 532 procedure TBaseCommThreadDispatch.TerminateActiveThreads; 533 var 534 i: Integer; 535 begin 536 if FCommThreadDispatchState = ctsActive then 537 begin 538 // Lock threads 539 FCSCommThreads.Acquire; 540 try 541 FCommThreadDispatchState := ctsTerminating; 542 543 DoOnStateChange; 544 545 // Terminate each thread in turn 546 for i := 0 to pred(FActiveThreads.Count) do 547 TCommThread(FActiveThreads[i]).Terminate; 548 finally 549 FCSCommThreads.Release; 550 end; 551 end; 552 end; 553 554 555 { TCommThreadParams } 556 557 procedure TCommThreadParams.Clear; 558 begin 559 FThreadParams.Clear; 560 FThreadObjects.Clear; 561 end; 562 563 constructor TCommThreadParams.Create; 564 begin 565 FThreadParams := TThreadParams.Create; 566 FThreadObjects := TThreadObjects.Create; 567 end; 568 569 destructor TCommThreadParams.Destroy; 570 begin 571 FreeAndNil(FThreadParams); 572 FreeAndNil(FThreadObjects); 573 574 inherited; 575 end; 576 577 function TCommThreadParams.GetObject(const ObjectName: String): TObject; 578 begin 579 Result := FThreadObjects.Items[ObjectName]; 580 end; 581 582 function TCommThreadParams.GetParam(const ParamName: String): Variant; 583 begin 584 Result := FThreadParams.Items[ParamName]; 585 end; 586 587 function TCommThreadParams.SetObject(const ObjectName: String; 588 Obj: TObject): TCommThreadParams; 589 begin 590 FThreadObjects.AddOrSetValue(ObjectName, Obj); 591 592 Result := Self; 593 end; 594 595 function TCommThreadParams.SetParam(const ParamName: String; 596 ParamValue: Variant): TCommThreadParams; 597 begin 598 FThreadParams.AddOrSetValue(ParamName, ParamValue); 599 600 Result := Self; 601 end; 602 603 { TCommQueueItem } 604 605 destructor TCommQueueItem.Destroy; 606 begin 607 if Assigned(FCommThreadParams) then 608 FreeAndNil(FCommThreadParams); 609 610 inherited; 611 end; 612 613 614 { TBaseStatusCommThreadDispatch } 615 616 procedure TBaseStatusCommThreadDispatch.DoOnReceiveThreadMessage( 617 Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); 618 begin 619 inherited; 620 621 case MessageId of 622 // Status Message 623 CTID_STATUS: DoOnStatus(Sender, 624 Name, 625 CommThreadParams.GetParam(PRM_STATUS_TEXT), 626 CommThreadParams.GetParam(PRM_STATUS_TYPE)); 627 // Progress Message 628 CTID_PROGRESS: DoOnProgress(Sender, 629 CommThreadParams.GetParam(PRM_PROGRESS_ID), 630 CommThreadParams.GetParam(PRM_PROGRESS), 631 CommThreadParams.GetParam(PRM_PROGRESS_MAX)); 632 end; 633 end; 634 635 procedure TBaseStatusCommThreadDispatch.DoOnStatus(Sender: TObject; const ID, 636 StatusText: String; StatusType: Integer); 637 begin 638 if (not (csDestroying in ComponentState)) and (Assigned(FOnStatus)) then 639 FOnStatus(Self, Sender, ID, StatusText, StatusType); 640 end; 641 642 procedure TBaseStatusCommThreadDispatch.DoOnProgress(Sender: TObject; 643 const ID: String; Progress, ProgressMax: Integer); 644 begin 645 if not (csDestroying in ComponentState) and (Assigned(FOnProgress)) then 646 FOnProgress(Self, Sender, ID, Progress, ProgressMax); 647 end; 648 649 end.
To use the library, simply descend your thread from the TCommThread thread and override the Execute procedure:
MyCommThreadObject = class(TCommThread) public procedure Execute; override; end;
Next, create a descendant of the TStatusCommThreadDispatch component and set it's events.
MyCommThreadComponent := TStatusCommThreadDispatch.Create(Self); // Add the event handlers MyCommThreadComponent.OnStateChange := OnStateChange; MyCommThreadComponent.OnReceiveThreadMessage := OnReceiveThreadMessage; MyCommThreadComponent.OnStatus := OnStatus; MyCommThreadComponent.OnProgress := OnProgress; // Set the thread class MyCommThreadComponent.CommThreadClass := TMyCommThread;
Make sure you set the CommThreadClass to your TCommThread descendant.
Now all you need to do is create the threads via MyCommThreadComponent:
FCommThreadComponent.NewThread .SetParam('MyThreadInputParameter', '12345') .SetObject('MyThreadInputObject', MyObject) .Start;
Add as many parameters and objects as you like. In your threads Execute method you can retrieve the parameters and objects.
MyThreadParameter := GetParam('MyThreadInputParameter'); // 12345 MyThreadObject := GetObject('MyThreadInputObject'); // MyObject
Parameters will be automatically freed. You need to manage objects yourself.
To send a message back to the main thread from the threads execute method:
FCommDispatchReceiver.QueueMessage(Self, CTID_MY_MESSAGE_ID, TCommThreadParams.Create .SetObject('MyThreadObject', MyThreadObject) .SetParam('MyThreadOutputParameter', MyThreadParameter));
Again, parameters will be destroyed automatically, objects you have to manage yourself.
To receive messages in the main thread either attach the OnReceiveThreadMessage event
or override the DoOnReceiveThreadMessage procedure:
procedure DoOnReceiveThreadMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); override;
Use the overridden procedure to process the messages sent back to your main thread:
procedure THostDiscovery.DoOnReceiveThreadMessage(Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); begin inherited; case MessageId of CTID_MY_MESSAGE_ID: begin // Process the CTID_MY_MESSAGE_ID message DoSomethingWithTheMessage(CommThreadParams.GetParam('MyThreadOutputParameter'), CommThreadParams.GeObject('MyThreadObject')); end; end; end;
The messages are pumped in the ProcessMessageQueue procedure.
This procedure is called via a TTimer.
If you use the component in a console app you will need to call ProcessMessageQueue manually.
The timer will start when the first thread is created.
It will stop when the last thread has finished.
If you need to control when the timer stops you can override the Finished procedure.
You can also perform actions depending on the state of the threads by overriding the DoOnStateChange procedure.
Take a look at the TCommThread descendant TStatusCommThreadDispatch.
It implements the sending of simple Status and Progress messages back to the main thread.
I hope this helps and that I've explained it OK.
This is related to my previous answer, but I was limited to 30000 characters.
Here's the code for a test app that uses TCommThread:
Test App (.pas)
unit frmMainU; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, ExtCtrls, StdCtrls, Threading.CommThread; type TMyCommThread = class(TCommThread) public procedure Execute; override; end; TfrmMain = class(TForm) Panel1: TPanel; lvLog: TListView; btnStop: TButton; btnNewThread: TButton; StatusBar1: TStatusBar; btn30NewThreads: TButton; tmrUpdateStatusBar: TTimer; procedure FormCreate(Sender: TObject); procedure btnStopClick(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure tmrUpdateStatusBarTimer(Sender: TObject); private FCommThreadComponent: TStatusCommThreadDispatch; procedure OnReceiveThreadMessage(Source, Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); procedure OnStateChange(Sender: TObject; State: TCommThreadDispatchState); procedure UpdateStatusBar; procedure OnStatus(Source, Sender: TObject; const ID: String; StatusText: String; StatusType: Integer); procedure OnProgress(Source, Sender: TObject; const ID: String; Progress, ProgressMax: Integer); public end; var frmMain: TfrmMain; implementation resourcestring StrStatusIDDProgre = 'StatusID: %s, Progress: %d, ProgressMax: %d'; StrActiveThreadsD = 'Active Threads: %d, State: %s'; StrIdle = 'Idle'; StrActive = 'Active'; StrTerminating = 'Terminating'; {$R *.dfm} { TMyCommThread } procedure TMyCommThread.Execute; var i: Integer; begin SendCommMessage(0, TCommThreadParams.Create.SetParam('status', 'started')); for i := 0 to 40 do begin sleep(50); SendStatusMessage(format('Thread: %s, i = %d', [Name, i]), 1); if Terminated then Break; sleep(50); SendProgressMessage(Integer(Self), i, 40, FALSE); end; if Terminated then SendCommMessage(0, TCommThreadParams.Create.SetParam('status', 'terminated')) else SendCommMessage(0, TCommThreadParams.Create.SetParam('status', 'finished')); end; { TfrmMain } procedure TfrmMain.btnStopClick(Sender: TObject); begin FCommThreadComponent.Stop; end; procedure TfrmMain.Button3Click(Sender: TObject); var i: Integer; begin for i := 0 to 29 do FCommThreadComponent.NewThread .SetParam('input_param1', 'test_value') .Start; end; procedure TfrmMain.Button4Click(Sender: TObject); begin FCommThreadComponent.NewThread .SetParam('input_param1', 'test_value') .Start; end; procedure TfrmMain.FormCreate(Sender: TObject); begin FCommThreadComponent := TStatusCommThreadDispatch.Create(Self); // Add the event handlers FCommThreadComponent.OnStateChange := OnStateChange; FCommThreadComponent.OnReceiveThreadMessage := OnReceiveThreadMessage; FCommThreadComponent.OnStatus := OnStatus; FCommThreadComponent.OnProgress := OnProgress; // Set the thread class FCommThreadComponent.CommThreadClass := TMyCommThread; end; procedure TfrmMain.OnProgress(Source, Sender: TObject; const ID: String; Progress, ProgressMax: Integer); begin With lvLog.Items.Add do begin Caption := '-'; SubItems.Add(format(StrStatusIDDProgre, [Id, Progress, ProgressMax])); end; end; procedure TfrmMain.OnReceiveThreadMessage(Source, Sender: TObject; MessageId: Integer; CommThreadParams: TCommThreadParams); begin if MessageID = 0 then With lvLog.Items.Add do begin Caption := IntToStr(MessageId); SubItems.Add(CommThreadParams.GetParam('status')); end; end; procedure TfrmMain.UpdateStatusBar; begin StatusBar1.SimpleText := format(StrActiveThreadsD, [FCommThreadComponent.ActiveThreadCount, FCommThreadComponent.StateText]); end; procedure TfrmMain.OnStateChange(Sender: TObject; State: TCommThreadDispatchState); begin With lvLog.Items.Add do begin case State of ctsIdle: Caption := StrIdle; ctsActive: Caption := StrActive; ctsTerminating: Caption := StrTerminating; end; end; end; procedure TfrmMain.OnStatus(Source, Sender: TObject; const ID: String; StatusText: String; StatusType: Integer); begin With lvLog.Items.Add do begin Caption := IntToStr(StatusType); SubItems.Add(StatusText); end; end; procedure TfrmMain.tmrUpdateStatusBarTimer(Sender: TObject); begin UpdateStatusBar; end; end.
Test app (.dfm)
object frmMain: TfrmMain Left = 0 Top = 0 Caption = 'CommThread Test' ClientHeight = 290 ClientWidth = 557 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate PixelsPerInch = 96 TextHeight = 13 object Panel1: TPanel AlignWithMargins = True Left = 3 Top = 3 Width = 97 Height = 265 Margins.Right = 0 Align = alLeft BevelOuter = bvNone TabOrder = 0 object btnStop: TButton AlignWithMargins = True Left = 0 Top = 60 Width = 97 Height = 25 Margins.Left = 0 Margins.Top = 10 Margins.Right = 0 Margins.Bottom = 0 Align = alTop Caption = 'Stop' TabOrder = 2 OnClick = btnStopClick end object btnNewThread: TButton Left = 0 Top = 0 Width = 97 Height = 25 Align = alTop Caption = 'New Thread' TabOrder = 0 OnClick = Button4Click end object btn30NewThreads: TButton Left = 0 Top = 25 Width = 97 Height = 25 Align = alTop Caption = '30 New Threads' TabOrder = 1 OnClick = Button3Click end end object lvLog: TListView AlignWithMargins = True Left = 103 Top = 3 Width = 451 Height = 265 Align = alClient Columns = < item Caption = 'Message ID' Width = 70 end item AutoSize = True Caption = 'Info' end> ReadOnly = True RowSelect = True TabOrder = 1 ViewStyle = vsReport end object StatusBar1: TStatusBar Left = 0 Top = 271 Width = 557 Height = 19 Panels = <> SimplePanel = True end object tmrUpdateStatusBar: TTimer Interval = 200 OnTimer = tmrUpdateStatusBarTimer Left = 272 Top = 152 end end