TCommThread -- 在delphi线程中实现消息循环

http://www.techques.com/question/1-4073197/How-do-I-send-and-handle-message-between-TService-parent-thread-and-child-thread?

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

 

posted @ 2014-10-10 16:33  IAmAProgrammer  阅读(750)  评论(0编辑  收藏  举报