Question/Problem/Abstract:
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.