{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: MTComponents.pas, released on 2000-09-22. The Initial Developer of the Original Code is Erwin Molendijk. Portions created by Erwin Molendijk are Copyright (C) 2002 Erwin Molendijk. All Rights Reserved. Contributor(s): ______________________________________. You may retrieve the latest version of this file at the Project JEDI home page, located at http://www.delphi-jedi.org Known Issues: -----------------------------------------------------------------------------} // $Id: JvMTComponents.pas 10612 2006-05-19 19:04:09Z jfudickar $ unit JvMTComponents; {$I jvcl.inc} interface uses {$IFDEF USEJVCL} {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} {$ENDIF USEJVCL} SysUtils, Classes, SyncObjs, Consts, {$IFDEF USEJVCL} JvComponentBase, {$ENDIF USEJVCL} JvMTThreading, JvMTConsts, JvMTData, JvMTSync, JvMTSyncMon; type {$IFDEF USEJVCL} TJvMTComponent = class(TJvComponent); {$ELSE} TJvMTComponent = class(TComponent); {$ENDIF USEJVCL} TJvMTSingleThread = class(TMTThread); TJvMTThread = class; TJvMTThreadEvent = procedure (Sender: TJvMTThread; MTThread: TJvMTSingleThread) of object; TJvMTManager = class(TJvMTComponent) private FManager: TMTManager; protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function AcquireNewThread: TJvMTSingleThread; function AcquireThread(Ticket: TMTTicket; var Thread: TJvMTSingleThread): Boolean; function ActiveThreads: Boolean; procedure ReleaseThread(Ticket: TMTTicket); procedure TerminateThreads; procedure WaitThreads; end; TJvMTManagedComponent = class(TJvMTComponent) private FManager: TJvMTManager; protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure SetManager(Value: TJvMTManager); virtual; published property Manager: TJvMTManager read FManager write SetManager; end; TJvMTThread = class(TJvMTManagedComponent) private FOnExecute: TJvMTThreadEvent; FOnFinished: TJvMTThreadEvent; FOnTerminating: TJvMTThreadEvent; FThread: TJvMTSingleThread; FRunOnCreate: Boolean; function GetStatus: TMTThreadStatus; function GetTicket: TMTTicket; procedure HookThread; procedure OnIntExecute(Thread: TMTThread); procedure OnIntFinished(Thread: TMTThread); procedure OnIntTerminating(Thread: TMTThread); procedure ReleaseThread; procedure SetOnExecute(Value: TJvMTThreadEvent); procedure SetOnFinished(Value: TJvMTThreadEvent); procedure SetOnTerminating(Value: TJvMTThreadEvent); procedure UnHookThread; protected procedure Loaded; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure SetManager(Value: TJvMTManager); override; procedure DoExecute(MTThread: TJvMTSingleThread); dynamic; procedure DoFinished(MTThread: TJvMTSingleThread); dynamic; procedure DoTerminating(MTThread: TJvMTSingleThread); dynamic; public destructor Destroy; override; procedure CheckTerminate; procedure Run; procedure RunCopy; procedure Synchronize(Method: TThreadMethod); procedure Terminate; procedure Wait; property Status: TMTThreadStatus read GetStatus; property Ticket: TMTTicket read GetTicket; published property RunOnCreate: Boolean read FRunOnCreate write FRunOnCreate; property OnExecute: TJvMTThreadEvent read FOnExecute write SetOnExecute; property OnFinished: TJvMTThreadEvent read FOnFinished write SetOnFinished; property OnTerminating: TJvMTThreadEvent read FOnTerminating write SetOnTerminating; end; TJvMTSectionBase = class(TJvMTComponent) private FSync: TSynchroObject; function GetActive: Boolean; procedure HookSync; protected procedure CheckInactiveProperty; procedure CreateSync; virtual; abstract; public destructor Destroy; override; procedure Enter; procedure Leave; published property Active: Boolean read GetActive; end; TJvMTSection = class(TJvMTSectionBase) private FAllowRecursion: Boolean; FInitEntered: Boolean; procedure SetAllowRecursion(Value: Boolean); procedure SetInitEntered(Value: Boolean); protected procedure CreateSync; override; public constructor Create(AOwner: TComponent); override; published property AllowRecursion: Boolean read FAllowRecursion write SetAllowRecursion default True; property InitEntered: Boolean read FInitEntered write SetInitEntered default False; end; TJvMTCountingSection = class(TJvMTSectionBase) private FInitCount: Integer; FMaxCount: Integer; procedure SetInitAndMax(Init,Max: Integer); procedure SetInitCount(Value: Integer); procedure SetMaxCount(Value: Integer); protected procedure CreateSync; override; public constructor Create(AOwner: TComponent); override; published property InitCount: Integer read FInitCount write SetInitCount default 0; property MaxCount: Integer read FMaxCount write SetMaxCount default 1; end; TJvMTAsyncBufferBase = class(TJvMTComponent) private FBuffer: TMTAsyncBuffer; FHooking: TCriticalSection; FMaxBufferSize: Integer; procedure SetMaxBufferSize(Value: Integer); protected procedure CreateBuffer; virtual; abstract; procedure HookBuffer; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function Read: TObject; procedure Write(AObject: TObject); published property MaxBufferSize: Integer read FMaxBufferSize write SetMaxBufferSize default MTDefaultBufferSize; end; TJvMTThreadToVCL = class(TJvMTAsyncBufferBase) private FOnCanRead: TNotifyEvent; protected procedure DoCanRead(Sender: TObject); dynamic; procedure CreateBuffer; override; published property OnCanRead: TNotifyEvent read FOnCanRead write FOnCanRead; end; TJvMTVCLToThread = class(TJvMTAsyncBufferBase) private FOnCanWrite: TNotifyEvent; protected procedure DoCanWrite(Sender: TObject); dynamic; procedure CreateBuffer; override; procedure Loaded; override; published property OnCanWrite: TNotifyEvent read FOnCanWrite write FOnCanWrite; end; TJvMTThreadToThread = class(TJvMTComponent) private FHooking: TCriticalSection; FMaxBufferSize: Integer; FQueue: TMTBoundedQueue; procedure HookQueue; procedure SetMaxBufferSize(Value: Integer); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function Read: TObject; procedure Write(AObject: TObject); published property MaxBufferSize: Integer read FMaxBufferSize write SetMaxBufferSize default MTDefaultBufferSize; end; TJvMTMonitorSection = class(TJvMTComponent) private FMonitor: TMTMonitor; function GetCondition(ID: Integer): TMTCondition; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Enter; procedure Leave; property Condition[ID: Integer]: TMTCondition read GetCondition; default; end; {$IFDEF USEJVCL} {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvMTComponents.pas $'; Revision: '$Revision: 10612 $'; Date: '$Date: 2006-05-19 21:04:09 +0200 (ven., 19 mai 2006) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} {$ENDIF USEJVCL} implementation {$IFDEF USEJVCL} uses JvResources; {$ENDIF USEJVCL} {$IFNDEF USEJVCL} resourcestring RsENoThreadManager = 'No ThreadManager specified'; RsEOperatorNotAvailable = 'Operation not available while thread is active'; RsECannotChangePropertySection = 'Cannot change property of active section'; RsECannotChangePropertyBuffer = 'Cannot change property of active buffer'; {$ENDIF !USEJVCL} constructor TJvMTManager.Create(AOwner: TComponent); begin inherited Create(AOwner); // We want to know about the form going down if AOwner <> nil then AOwner.FreeNotification(Self); // hook to a manager object if not designing in the IDE if not (csDesigning in ComponentState) then FManager := TMTManager.Create; end; destructor TJvMTManager.Destroy; begin // call inherited destroy, this will send Notification's to all the mtcThread // components. These will release all their threads. inherited Destroy; // Now all threads have been released. // Free the manager and the threads belonging to this manager. FManager.Free; end; function TJvMTManager.AcquireNewThread: TJvMTSingleThread; begin Result := TJvMTSingleThread(FManager.AcquireNewThread); end; function TJvMTManager.AcquireThread(Ticket: TMTTicket; var Thread: TJvMTSingleThread): Boolean; begin Result := FManager.AcquireThread(Ticket, TMTThread(Thread)); end; function TJvMTManager.ActiveThreads: Boolean; begin Result := FManager.ActiveThreads; end; procedure TJvMTManager.Notification(AComponent: TComponent; Operation: TOperation); begin // check if the form is being destroyed if (not (csDesigning in ComponentState)) and (Operation = opRemove) and (AComponent = Owner) then begin // form is going down: terminate all threads TerminateThreads; // and wait until all is well WaitThreads; end; inherited Notification(AComponent, Operation); end; procedure TJvMTManager.ReleaseThread(Ticket: TMTTicket); begin FManager.ReleaseThread(Ticket); end; procedure TJvMTManager.TerminateThreads; begin FManager.TerminateThreads; end; procedure TJvMTManager.WaitThreads; begin FManager.WaitThreads; end; //=== { TJvMTManagedComponent } ============================================== procedure TJvMTManagedComponent.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation = opRemove) and (AComponent = FManager) then FManager := nil; // important during designtime inherited Notification(AComponent, Operation); end; procedure TJvMTManagedComponent.SetManager(Value: TJvMTManager); begin if Assigned(FManager) then FManager.RemoveFreeNotification(Self); FManager := Value; if Assigned(FManager) then FManager.FreeNotification(Self); end; //=== { TJvMTThread } ======================================================== destructor TJvMTThread.Destroy; begin ReleaseThread; inherited Destroy; end; procedure TJvMTThread.CheckTerminate; begin HookThread; FThread.CheckTerminate; end; function TJvMTThread.GetStatus: TMTThreadStatus; begin HookThread; Result := FThread.Status; end; function TJvMTThread.GetTicket: TMTTicket; begin HookThread; Result := FThread.Ticket; end; procedure TJvMTThread.HookThread; begin if FThread = nil then begin if FManager = nil then raise EThread.CreateRes(@RsENoThreadManager); // get the new thread FThread := FManager.AcquireNewThread; // hook up the nessesary events if Assigned(FOnExecute) then FThread.OnExecute := OnIntExecute; if Assigned(FOnTerminating) then FThread.OnTerminating := OnIntTerminating; if Assigned(FOnFinished) then FThread.OnFinished := OnIntFinished; // give it a name FThread.Name := Name; end; end; procedure TJvMTThread.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation = opRemove) and (AComponent = FManager) then ReleaseThread; // important during runtime // now can inherited (this wil invalidate FManager) inherited Notification(AComponent, Operation); end; procedure TJvMTThread.OnIntExecute(Thread: TMTThread); begin DoExecute(TJvMTSingleThread(Thread)); end; procedure TJvMTThread.OnIntFinished(Thread: TMTThread); begin DoFinished(TJvMTSingleThread(Thread)); end; procedure TJvMTThread.OnIntTerminating(Thread: TMTThread); begin DoTerminating(TJvMTSingleThread(Thread)); end; procedure TJvMTThread.ReleaseThread; begin // check if there is an acquired thread if FThread <> nil then begin // release the thread and invalidate the pointer FThread.Release; FThread := nil; end; end; procedure TJvMTThread.Run; begin HookThread; FThread.Run; end; procedure TJvMTThread.RunCopy; begin ReleaseThread; Run; end; procedure TJvMTThread.SetManager(Value: TJvMTManager); begin UnHookThread; inherited SetManager(Value); end; procedure TJvMTThread.SetOnExecute(Value: TJvMTThreadEvent); begin UnHookThread; FOnExecute := Value; end; procedure TJvMTThread.SetOnFinished(Value: TJvMTThreadEvent); begin UnHookThread; FOnFinished := Value; end; procedure TJvMTThread.SetOnTerminating(Value: TJvMTThreadEvent); begin UnHookThread; FOnTerminating := Value; end; procedure TJvMTThread.Synchronize(Method: TThreadMethod); begin HookThread; FThread.Synchronize(Method); end; procedure TJvMTThread.Terminate; begin HookThread; FThread.Terminate; end; procedure TJvMTThread.UnHookThread; begin if FThread <> nil then begin if FThread.Status in [tsInitializing, tsFinished] then begin FThread.Terminate; {incase initializing} FThread.Release; FThread := nil; end else raise EThread.CreateRes(@RsEOperatorNotAvailable); end; end; procedure TJvMTThread.Wait; begin HookThread; FThread.Wait; end; procedure TJvMTThread.DoExecute(MTThread: TJvMTSingleThread); begin if Assigned(FOnExecute) then FOnExecute(Self, MTThread); end; procedure TJvMTThread.DoFinished(MTThread: TJvMTSingleThread); begin if Assigned(FOnFinished) then FOnFinished(Self, MTThread); end; procedure TJvMTThread.DoTerminating(MTThread: TJvMTSingleThread); begin if Assigned(FOnTerminating) then FOnTerminating(Self, MTThread); end; procedure TJvMTThread.Loaded; begin inherited Loaded; // Component is ready. Shall we start a thread? if (not (csDesigning in ComponentState)) and FRunOnCreate then Run; end; //=== { TJvMTSectionBase } =================================================== destructor TJvMTSectionBase.Destroy; begin // signal interested components that we are going down inherited Destroy; // cleanup FSync.Free; end; procedure TJvMTSectionBase.CheckInactiveProperty; begin if Active then raise EThread.CreateRes(@RsECannotChangePropertySection); end; procedure TJvMTSectionBase.Enter; begin HookSync; FSync.Acquire; end; function TJvMTSectionBase.GetActive: Boolean; begin Result := FSync <> nil; end; procedure TJvMTSectionBase.HookSync; begin if not Active then CreateSync; end; procedure TJvMTSectionBase.Leave; begin HookSync; FSync.Release; end; //=== { TJvMTSection } ======================================================= constructor TJvMTSection.Create(AOwner: TComponent); begin inherited Create(AOwner); FAllowRecursion := True; end; procedure TJvMTSection.CreateSync; begin if FAllowRecursion then FSync := TMTCriticalSection.Create(Name) else FSync := TMTMutex.Create(Name); if FInitEntered then Enter; end; procedure TJvMTSection.SetAllowRecursion(Value: Boolean); begin CheckInactiveProperty; FAllowRecursion := Value; end; procedure TJvMTSection.SetInitEntered(Value: Boolean); begin CheckInactiveProperty; FInitEntered := Value; end; //=== { TJvMTCountingSection } =============================================== constructor TJvMTCountingSection.Create(AOwner: TComponent); begin inherited Create(AOwner); FMaxCount := 1; end; procedure TJvMTCountingSection.CreateSync; begin FSync := TMTSemaphore.Create(FMaxCount-FInitCount, FMaxCount, Name); end; procedure TJvMTCountingSection.SetInitAndMax(Init,Max: Integer); begin CheckInactiveProperty; if (Max < 1) or (Init < 0) or (Init > Max) then raise EInvalidOperation.CreateResFmt(@SPropertyOutOfRange, [ClassName]); FInitCount := Init; FMaxCount := Max; end; procedure TJvMTCountingSection.SetInitCount(Value: Integer); begin SetInitAndMax(Value, FMaxCount); end; procedure TJvMTCountingSection.SetMaxCount(Value: Integer); begin SetInitAndMax(FInitCount, Value); end; //=== { TJvMTAsyncBufferBase } =============================================== constructor TJvMTAsyncBufferBase.Create(AOwner: TComponent); begin inherited Create(AOwner); FMaxBufferSize := MTDefaultBufferSize; FHooking := TCriticalSection.Create; end; destructor TJvMTAsyncBufferBase.Destroy; begin // notify interested components inherited Destroy; // cleanup FBuffer.Free; FHooking.Free; end; procedure TJvMTAsyncBufferBase.HookBuffer; begin // buffer still uncreated? if FBuffer = nil then begin // enter critical section FHooking.Enter; try // perform check again. and create if we are the first in this section if FBuffer = nil then CreateBuffer; finally FHooking.Leave; end; end; end; function TJvMTAsyncBufferBase.Read: TObject; begin HookBuffer; Result := FBuffer.Read; end; procedure TJvMTAsyncBufferBase.SetMaxBufferSize(Value: Integer); begin if FBuffer <> nil then raise EThread.CreateRes(@RsECannotChangePropertyBuffer); FMaxBufferSize := Value; end; procedure TJvMTAsyncBufferBase.Write(AObject: TObject); begin HookBuffer; FBuffer.Write(AObject); end; //=== { TJvMTThreadToVCL } =================================================== procedure TJvMTThreadToVCL.CreateBuffer; begin FBuffer := TMTBufferToVCL.Create(FMaxBufferSize, Name); TMTBufferToVCL(FBuffer).OnCanRead := DoCanRead; end; procedure TJvMTThreadToVCL.DoCanRead(Sender: TObject); begin // call the OnCanRead event with this object as the sender if Assigned(FOnCanRead) then FOnCanRead(Self); end; //=== { TJvMTVCLToThread } =================================================== procedure TJvMTVCLToThread.CreateBuffer; begin FBuffer := TMTVCLToBuffer.Create(FMaxBufferSize, Name); TMTVCLToBuffer(FBuffer).OnCanWrite := DoCanWrite; end; procedure TJvMTVCLToThread.DoCanWrite(Sender: TObject); begin // call the OnCanWrite event with this object as the sender if Assigned(FOnCanWrite) then FOnCanWrite(Self); end; procedure TJvMTVCLToThread.Loaded; begin inherited Loaded; // force first Event HookBuffer; if Assigned(FOnCanWrite) then FOnCanWrite(Self); end; //=== { TJvMTThreadToThread } ================================================ constructor TJvMTThreadToThread.Create(AOwner: TComponent); begin inherited Create(AOwner); FMaxBufferSize := MTDefaultBufferSize; FHooking := TCriticalSection.Create; end; destructor TJvMTThreadToThread.Destroy; begin inherited Destroy; FQueue.Free; FHooking.Free; end; procedure TJvMTThreadToThread.HookQueue; begin // buffer still uncreated? if FQueue = nil then begin // enter critical section FHooking.Enter; try // perform check again. and create if we are the first in this section if FQueue = nil then FQueue := TMTBoundedQueue.Create(FMaxBufferSize,Name); finally FHooking.Leave; end; end; end; function TJvMTThreadToThread.Read: TObject; begin HookQueue; Result := FQueue.Pop; end; procedure TJvMTThreadToThread.SetMaxBufferSize(Value: Integer); begin if FQueue <> nil then raise EThread.CreateRes(@RsECannotChangePropertyBuffer); if Value < 1 then raise EInvalidOperation.CreateResFmt(@SPropertyOutOfRange, [ClassName]); FMaxBufferSize := Value; end; procedure TJvMTThreadToThread.Write(AObject: TObject); begin HookQueue; FQueue.Push(AObject); end; //=== { TJvMTMonitorSection } ================================================ constructor TJvMTMonitorSection.Create(AOwner: TComponent); begin inherited Create(AOwner); FMonitor := TMTMonitor.Create; end; destructor TJvMTMonitorSection.Destroy; begin FMonitor.Free; inherited Destroy; end; procedure TJvMTMonitorSection.Enter; begin FMonitor.Enter; end; function TJvMTMonitorSection.GetCondition(ID: Integer): TMTCondition; begin Result := FMonitor.Condition[ID]; end; procedure TJvMTMonitorSection.Leave; begin FMonitor.Leave; end; {$IFDEF USEJVCL} {$IFDEF UNITVERSIONING} initialization RegisterUnitVersion(HInstance, UnitVersioning); finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} {$ENDIF USEJVCL} end.