Mega Code Archive

 
Categories / Delphi / Examples
 

Smart threads with a central management

Title: Smart threads with a central management Question: Ever wanted to fire up some threads in your application, let them do some time consuming stuff and then report the results to the user? This caused some synchronisation trouble, didn't it? Shutting down your app while threads where still running, updating the user interface... Here is a unit that will give a good bases to avoid all kinds of multi threading trouble. Answer: { ----------------------------------------------------------------------- Newer version and test bench can be found here: http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=17700 ----------------------------------------------------------------------- Smart Thread Lib Version 1.01 Copyright (c) 2002 by DelphiFactory Netherlands BV What is it: Provides an easy way to use threads. Usage: Create your threads as TSmartThreads and manage them using the SmartThreadManager global object. For more information about threads in delphi: http://www.pergolesi.demon.co.uk/prog/threads/ToC.html For example on how to use this unit for with a Indy blocking socket TCP/IP client: http://www.delphi3000.com/articles/article_3047.asp } unit SmartThreadLib; { Defining the DefaultMessageHandler causes the messages send by the threads to be displayed on screen if no OnMessage handler is assigned. This is only for debugging purposes (as GUI routines should not be located in this unit). } {$DEFINE DefaultMessageHandler} interface uses SysUtils, Classes, Contnrs {$IFDEF DefaultMessageHandler} ,QDialogs {$ENDIF} ; resourcestring SForcedStop = 'Thread ''%s'' forced to stop'; { EThreadForcedShutdown exception will be raised inside a thread when it has to stop running. } type EThreadForcedShutdown = class(Exception); { The ThreadMessageEvent is called by a smart thread but within the context of the main thread and provides the ability to easily show messages to the user. } type TThreadMessageEvent = procedure(Sender : TObject; const AMessage : string) of object; { The SmartThread. Usage: 1. Create a descendent class. 2. Override the SmartExecute. 3. Call Check from within SmartExecute on a regular base. This routine will raise an EThreadForcedShutdown exception if the thread has to stop. The exception is handled by this base class, you do not need to handle it. Additional tips: - You can use the Msg() procedure to show messages to the user without having to worry about synchronisation problems. - You can override GetMustStop() to add additional checks that could cause a thread to do a forced shutdown. - SmartExecute is started directly after calling Create() - The thread is FreeOnTerminate. - SmartThreads are based on the idea that threads are independant. You should not keep a pointer to the new thread, because you can never know if this pointer is still valid. Instead let your threads communicate using a global object. As an example se the SmartThreadManager. } type TSmartThread = class(TThread) private FMsg : string; procedure DoMessage; protected function GetMustStop: Boolean; virtual; procedure Msg(const Msg : string); virtual; procedure Check; procedure Execute; override; procedure SmartExecute; virtual; public constructor Create; virtual; property MustStop : Boolean read GetMustStop; end; { The SmartThreadManager: Global object that manages all TSmartThread's. The SmartThreads register themselfs at this manager before executing, and unregister just before destroying itself. - SmartThreads are based on the idea that threads are independant. You should not keep a pointer to the new thread, because you can never know if this pointer is still valid. Instead let your threads communicate using a global object. The manager provides an event called OnMessage. The threads can trigger this event by calling their Msg() method. The OnMessage event runs in the context of the main thread. So screen updates can be performed. The Sender parameter is the thread which has send the message. This thread is guarantied to exist and is in suspended mode during the execution of the eventhandler. (If 'DefaultMessageHandler' is defined during compilation, the message will be displayed automaticly when no handler is assigned.) - Set ShutDown to True to shutdown all the smart threads. - ThreadCount returns the number of currently running smart threads - All threads are terminated automaticaly when the manager is destroyed. The manager is created and destroyed by the initialization and finalization section in this unit. } type TSmartThreadManager = class private FThreadListSync : TMultiReadExclusiveWriteSynchronizer; FShutDownSync : TMultiReadExclusiveWriteSynchronizer; FThreadList : TObjectList; FShutDown : Boolean; FOnMessage : TThreadMessageEvent; function GetShutDown: Boolean; procedure SetShutDown(const Value: Boolean); function GetThreadCount: Integer; protected procedure RegisterThread(AThread : TSmartThread); procedure UnregisterThread(AThread : TSmartThread); procedure DoMessage(Sender : TObject; AMessage : string); public constructor Create; destructor Destroy; override; procedure LimitThreadCount(Max : Integer); property ThreadCount : Integer read GetThreadCount; property Shutdown : Boolean read GetShutDown write SetShutDown; property OnMessage : TThreadMessageEvent read FOnMessage write FOnMessage; end; var SmartThreadManager : TSmartThreadManager; implementation { TSmartThread } procedure TSmartThread.Check; begin // raise exception when the thread needs to stop if MustStop then raise EThreadForcedShutdown.CreateFmt(SForcedStop, [Self.ClassName]); end; constructor TSmartThread.Create; begin // create in suspended mode inherited Create(True); // init FreeOnTerminate := True; // register at the manager SmartThreadManager.RegisterThread(Self); // run the thread Suspended := False; end; procedure TSmartThread.DoMessage; { Call this method using Synchronize(DoMessage) to make sure that we are running in the context of the main thread } begin // Notify the manager about the message SmartThreadManager.DoMessage(Self, FMsg); end; procedure TSmartThread.Execute; begin try try // Perform code to be implemented by descendant class SmartExecute; except // ignore forced shutdown exceptions On E : EThreadForcedShutdown do {nothing}; end; finally // unregister at the manager SmartThreadManager.UnregisterThread(Self); end; // After unregistering the smart thread should shutdown // as fast as possible and do not perform any more tasks. end; function TSmartThread.GetMustStop: Boolean; begin // We must stop if the thread is marked as terminated // or if the manager wants to shutdown Result := Terminated or SmartThreadManager.Shutdown; end; procedure TSmartThread.Msg(const Msg: string); begin // save message for later use by DoMessage FMsg := Msg; // call the DoMessage in the context of the main thread Synchronize(DoMessage); end; procedure TSmartThread.SmartExecute; begin // do nothing, method can be implemented by descendant end; { TSmartThreadManager } constructor TSmartThreadManager.Create; begin inherited Create; // init FShutdownSync := TMultiReadExclusiveWriteSynchronizer.Create; FThreadListSync := TMultiReadExclusiveWriteSynchronizer.Create; FThreadList := TObjectList.Create(False); end; destructor TSmartThreadManager.Destroy; begin // manager is shutting down - cause al threads to stop SetShutDown(True); // wait for all threads to have stopped LimitThreadCount(0); // now we can cleanup FThreadList.Free; FThreadListSync.Free; FShutDownSync.Free; inherited Destroy; end; procedure TSmartThreadManager.DoMessage(Sender: TObject; AMessage: string); const SMsg = '%s message: ''%s'''; begin // Call eventhandler if Assigned(FOnMessage) then FOnMessage(Sender, AMessage) {$IFDEF DefaultMessageHandler} else // if there is no eventhandler, display the message on screen ShowMessage(Format(SMsg, [Sender.ClassName, AMessage])); {$ENDIF} end; function TSmartThreadManager.GetShutDown: Boolean; { ThreadSafe Returns the Shutdown flag } begin FShutdownSync.BeginRead; try Result := FShutDown; finally FShutdownSync.EndRead; end; end; function TSmartThreadManager.GetThreadCount: Integer; { ThreadSafe Returns the number of running smart threads } begin FThreadListSync.BeginRead; try Result := FThreadList.Count; finally FThreadListSync.EndRead; end; end; procedure TSmartThreadManager.LimitThreadCount(Max: Integer); { Should only be called in the context of the main thread. Returns until the number of runnning smart threads is equal or lower then the Max parameter. } begin while GetThreadCount Max do if not CheckSynchronize then Sleep(100); end; procedure TSmartThreadManager.RegisterThread(AThread: TSmartThread); { Thread safe Is called by the TSmartThread.Create constructor to register a new smart thread. } begin FThreadListSync.BeginWrite; try if FThreadList.IndexOf(AThread) = -1 then FThreadList.Add(AThread); finally FThreadListSync.EndWrite; end; end; procedure TSmartThreadManager.SetShutDown(const Value: Boolean); { Thread Safe Set the shutdown flag. } begin // make sure this is an different value if Value GetShutDown then begin FShutdownSync.BeginWrite; try // set new value FShutDown := Value; finally FShutdownSync.EndWrite; end; end; end; procedure TSmartThreadManager.UnregisterThread(AThread: TSmartThread); { Thread Safe Called by TSmartThread.Execute after the TSmartThread.SmartExecute has finished (or an exception was raised). it unregisters the thread. } begin FThreadListSync.BeginWrite; try FThreadList.Remove(AThread) finally FThreadListSync.EndWrite; end; end; initialization // fire up the manager SmartThreadManager := TSmartThreadManager.Create; finalization // going down SmartThreadManager.Free; end.