2008. április 1., kedd

Smart threads with a central management


Problem/Question/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:
   "SmartThreadLib example: Using blocking Indy sockets in a thread" article
}

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.

Nincsenek megjegyzések:

Megjegyzés küldése