delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback



Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi








Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (2)


Smart threads with a central managementGo to <B>Erwin</B> Molendijk's websiteFormat this article printer-friendly!Bookmark function is only available for registered users!
The TSmartThread class and the SmartThreadManager object
Product:
Delphi 6.x (or higher)
Category:
Object Pascal
Skill Level:
Scoring:
Last Update:
03/19/2002
Search Keys:
delphi delphi3000 article borland vcl code-snippet tthread thread smartthread threadmanager synchronize
Times Scored:
7
Visits:
7840
Uploader: Erwin Molendijk
Company: Carvix & Delfer
Reference: http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=17700
 
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.





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
Another approach
    William Egge (Mar 16 2002 11:01PM)

I have an artical on reducing complexity of this type. see http://www.delphi3000.com/articles/article_3079.asp
You could do the same with less coding that you have and there is no need to create a manager.

Respond

Syncronize
    Yoav Abrahami (Feb 21 2002 4:11PM)

The syncronize function, fom my expirience, is not the best method to synchronize threads. The problem is that the function is using windows messages, and preforms some unpredicted side affects and errors.
Messages are good when you wish to display results on the screen, not as a general sync. tool.

A better method to syncronize is using semaphors, critical sections, mutex, monitors, etc.
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


   


  Community Ad of
E. DSpirito
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)