Componentes.Terceros.jvcl/official/3.00/run/JvMTThreading.pas

850 lines
20 KiB
ObjectPascal

{-----------------------------------------------------------------------------
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: MTThreading.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: JvMTThreading.pas,v 1.34 2005/10/28 08:37:23 marquardt Exp $
unit JvMTThreading;
{$I jvcl.inc}
interface
uses
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
JclUnitVersioning,
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
SysUtils, Classes, SyncObjs, Contnrs,
{$IFDEF MSWINDOWS}
Windows, Messages,
{$ENDIF MSWINDOWS}
{$IFDEF HAS_UNIT_LIBC}
Libc,
{$ENDIF HAS_UNIT_LIBC}
{$IFDEF UNIX}
QWindows,
{$ENDIF UNIX}
JvMTConsts, JvMTSync, JvVCL5Utils;
type
TMTManager = class;
TMTThread = class;
TMTEvent = procedure(Thread: TMTThread) of object;
{$IFDEF COMPILER5}
TIntThread = class(Classes.TThread)
public
destructor Destroy; override;
procedure Synchronize(Method: TThreadMethod);
function WaitFor: Longword;
end;
{$ELSE}
TIntThread = TThread;
{$ENDIF COMPILER5}
TMTInternalThread = class(TIntThread)
private
FName: string;
FOnExecute: TNotifyEvent;
protected
procedure Execute; override;
procedure RaiseName;
public
property Name: string read FName write FName;
property OnExecute: TNotifyEvent read FOnExecute write FOnExecute;
end;
TMTThread = class(TObject)
private
FFinished: Boolean;
FIntThread: TMTInternalThread;
FManager: TMTManager;
FName: string;
FOnExecute: TMTEvent;
FOnFinished: TMTEvent;
FOnTerminating: TMTEvent;
FReferenceCount: Integer;
FStatusChange: TCriticalSection;
FTerminateSignal: THandle;
FTicket: TMTTicket;
procedure CreateAndRun;
function GetStatus: TMTThreadStatus;
procedure Log(const Msg: string);
procedure OnIntThreadExecute(Sender: TObject);
procedure OnIntThreadTerminate(Sender: TObject);
{$IFDEF COMPILER5}
procedure SyncOnIntThreadTerminate;
{$ENDIF COMPILER5}
procedure SetName(const Value: string);
protected
procedure DecRef;
procedure IncRef;
public
constructor Create(Manager: TMTManager; Ticket: Integer);
destructor Destroy; override;
procedure CheckTerminate;
procedure Release;
procedure Run;
procedure Synchronize(Method: TThreadMethod);
procedure Terminate;
procedure Wait;
property Name: string read FName write SetName;
property OnExecute: TMTEvent read FOnExecute write FOnExecute;
property OnFinished: TMTEvent read FOnFinished write FOnFinished;
property OnTerminating: TMTEvent read FOnTerminating write FOnTerminating;
property ReferenceCount: Integer read FReferenceCount;
property Status: TMTThreadStatus read GetStatus;
property TerminateSignal: THandle read FTerminateSignal;
property ThreadManager: TMTManager read FManager;
property Ticket: TMTTicket read FTicket;
end;
TMTManager = class(TObject)
private
FGenTicket: TCriticalSection;
FNextTicket: TMTTicket;
FThreads: TObjectList;
FThreadsChange: TCriticalSection;
function FindThread(Ticket: TMTTicket; var Thread: TMTThread): Boolean;
function GenerateTicket: TMTTicket;
procedure Log(const Msg: string);
procedure TryRemoveThread(Thread: TMTThread);
function InternalActiveThreads(RaiseID: Longword): Integer;
protected
procedure OnThreadFinished(Thread: TMTThread);
public
constructor Create;
destructor Destroy; override;
function AcquireNewThread: TMTThread;
function AcquireThread(Ticket: TMTTicket; var Thread: TMTThread): Boolean;
function ActiveThreads: Boolean;
procedure ReleaseThread(Ticket: TMTTicket);
procedure TerminateThreads;
procedure WaitThreads;
end;
function CurrentMTThread: TMTThread;
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$RCSfile: JvMTThreading.pas,v $';
Revision: '$Revision: 1.34 $';
Date: '$Date: 2005/10/28 08:37:23 $';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
implementation
{$IFDEF USEJVCL}
uses
JvResources;
{$ENDIF USEJVCL}
{$IFNDEF USEJVCL}
resourcestring
RsECurThreadIsPartOfManager = 'Current MTThread is part of the MTManager';
RsECheckTerminateCalledByWrongThread = 'CheckTerminate can only be called by the same thread';
RsEThreadNotInitializedOrWaiting = 'Cannot run: thread is not Initializing or Waiting';
RsECannotChangeNameOfOtherActiveThread = 'Cannot change name of other active thread';
RsEReleaseOfUnusedTicket = 'Release of unused ticket';
{$ENDIF !USEJVCL}
threadvar
_CurrentMTThread: TMTThread;
function CurrentMTThread: TMTThread;
begin
Result := _CurrentMTThread;
end;
{$IFDEF COMPILER5}
type
PSyncRequest = ^TSyncRequest;
TSyncRequest = record
Method: TThreadMethod;
ExceptionObject: TObject;
Signal: THandle;
end;
var
SyncRequestAvailable: Boolean;
ThreadSyncLock: TRTLCriticalSection;
SyncRequestList: TList = nil;
SyncWindow: HWND;
function CheckSynchronize: Boolean;
var
SyncRequest: PSyncRequest;
begin
Result := False;
// Only the main thread is allowed to synchronize thread methods.
if GetCurrentThreadID <> MainThreadID then
Exit;
EnterCriticalSection(ThreadSyncLock);
try
if SyncRequestAvailable and (SyncRequestList <> nil) then
begin
// Do not block while another thread is adding a new synchronization request.
while SyncRequestList.Count > 0 do
begin
SyncRequest := SyncRequestList[0];
SyncRequestList.Delete(0);
try
SyncRequest.Method;
except
SyncRequest^.ExceptionObject := ExceptObject;
end;
// inform TIntThread.Synchronize
SetEvent(SyncRequest.Signal);
SyncRequestAvailable := False;
Result := True;
end;
end;
finally
LeaveCriticalSection(ThreadSyncLock);
end;
end;
procedure FinalizeSyncRequestList;
begin
// if the list is not empty there are still waiting threads
if SyncRequestList <> nil then
begin
CheckSynchronize;
SyncRequestList.Free;
SyncRequestList := nil;
end;
end;
function SyncWndProc(wnd: HWND; Msg: Cardinal; wParam, lParam: Integer): Integer; stdcall;
begin
if Msg = WM_USER + 1 then
Result := Integer(CheckSynchronize)
else
Result := DefWindowProc(wnd, Msg, wParam, lParam);
end;
//=== { TIntThread } =========================================================
procedure TIntThread.Synchronize(Method: TThreadMethod);
var
SyncRequest: TSyncRequest;
begin
if GetCurrentThreadID = MainThreadID then
Method
else
begin
SyncRequest.Signal := CreateEvent(nil, True, False, nil);
try
EnterCriticalSection(ThreadSyncLock);
try
if SyncRequestList = nil then
SyncRequestList := TList.Create;
SyncRequest.ExceptionObject := nil;
SyncRequest.Method := Method;
// The function returns only when the item is deleted from the List.
SyncRequestList.Add(@SyncRequest);
SyncRequestAvailable := True;
finally
LeaveCriticalSection(ThreadSyncLock);
end;
PostMessage(SyncWindow, WM_USER + 1, 0, 0);
// Wait for CheckSynchronize.
WaitForSingleObject(SyncRequest.Signal, INFINITE);
finally
CloseHandle(SyncRequest.Signal);
end;
// An exception occured. Re-raise it in the calling thread's context.
if Assigned(SyncRequest.ExceptionObject) then
raise SyncRequest.ExceptionObject;
end;
end;
destructor TIntThread.Destroy;
begin
CheckSynchronize;
inherited Destroy;
end;
function TIntThread.WaitFor: Longword;
begin
CheckSynchronize;
Result := inherited WaitFor;
end;
{$ENDIF COMPILER5}
//=== { TMTInternalThread } ==================================================
procedure TMTInternalThread.Execute;
begin
RaiseName;
if Assigned(FOnExecute) then
FOnExecute(Self);
end;
procedure TMTInternalThread.RaiseName;
{$IFDEF COMPILER7_UP}
var
ThreadNameInfo: TThreadNameInfo;
{$ENDIF COMPILER7_UP}
begin
{$IFDEF COMPILER7_UP}
ThreadNameInfo.FType := $1000;
ThreadNameInfo.FName := PChar(FName);
ThreadNameInfo.FThreadID := $FFFFFFFF;
ThreadNameInfo.FFlags := 0;
try
RaiseException($406D1388, 0, SizeOf(ThreadNameInfo) div SizeOf(Longword),
@ThreadNameInfo);
except
end;
{$ENDIF COMPILER7_UP}
end;
//=== { TMTThread } ==========================================================
constructor TMTThread.Create(Manager: TMTManager; Ticket: Integer);
begin
inherited Create;
FStatusChange := TCriticalSection.Create;
FManager := Manager;
FTicket := Ticket;
FName := 'MT' + IntToStr(Ticket); // do not localize
FTerminateSignal := CreateSemaphore(nil, 0, 1, '');
end;
destructor TMTThread.Destroy;
begin
CloseHandle(FTerminateSignal);
FStatusChange.Free;
inherited Destroy;
end;
procedure TMTThread.CheckTerminate;
begin
if CurrentMTThread <> Self then
raise EMTThreadError.CreateRes(@RsECheckTerminateCalledByWrongThread);
if Status = tsTerminating then
raise EMTTerminateError.Create('');
end;
procedure TMTThread.CreateAndRun;
begin
FStatusChange.Acquire;
try
FIntThread := TMTInternalThread.Create(True);
FIntThread.OnExecute := OnIntThreadExecute;
FIntThread.OnTerminate := OnIntThreadTerminate;
FIntThread.FreeOnTerminate := True;
FIntThread.Name := FName;
FIntThread.Resume;
finally
FStatusChange.Release;
end;
end;
procedure TMTThread.DecRef;
begin
InterlockedDecrement(FReferenceCount);
end;
function TMTThread.GetStatus: TMTThreadStatus;
begin
FStatusChange.Acquire;
try
if FFinished then
Result := tsFinished
else
if FIntThread = nil then
Result := tsInitializing
else
if FIntThread.Suspended then
Result := tsWaiting
else
if FIntThread.Terminated then
Result := tsTerminating
else
Result := tsRunning;
finally
FStatusChange.Release;
end;
end;
procedure TMTThread.IncRef;
begin
InterlockedIncrement(FReferenceCount);
end;
procedure TMTThread.Log(const Msg: string);
begin
// (rom) no OutputDebugString in production code
{$IFDEF DEBUGINFO_ON}
OutputDebugString(PChar('[' + ClassName + '] ' + Msg));
{$ENDIF DEBUGINFO_ON}
end;
procedure TMTThread.OnIntThreadExecute(Sender: TObject);
begin
// set the CurrentMTThread variable.
// this variable is global, but only to this thread.
_CurrentMTThread := Self;
// run OnExecute event
try
if Assigned(FOnExecute) then
FOnExecute(Self);
except
on E: EMTTerminateError do
{nothing};
on E: Exception do
Log('OnExecute Exception: "' + E.Message + '"'); // do not localize
end;
// make sure terminate flag is set
FIntThread.Terminate;
// run OnTerminating event
try
if Assigned(FOnTerminating) then
FOnTerminating(Self);
except
on E: Exception do
Log('OnTerminate Exception: "' + E.Message + '"'); // do not localize
end;
{$IFDEF COMPILER5}
FIntThread.OnTerminate := nil;
Synchronize(SyncOnIntThreadTerminate);
{$ENDIF COMPILER5}
end;
{$IFDEF COMPILER5}
procedure TMTThread.SyncOnIntThreadTerminate;
begin
OnIntThreadTerminate(Self);
end;
{$ENDIF COMPILER5}
procedure TMTThread.OnIntThreadTerminate(Sender: TObject);
begin
FStatusChange.Acquire;
try
if FFinished then
Exit;
FFinished := True;
finally
FStatusChange.Release;
end;
if Assigned(FOnFinished) then
FOnFinished(Self);
FStatusChange.Acquire;
try
FIntThread := nil;
finally
FStatusChange.Release;
end;
// After a call to OnThreadFinished, this object might be destroyed.
// So don't access any fields after this call.
FManager.OnThreadFinished(Self);
end;
procedure TMTThread.Release;
begin
FManager.ReleaseThread(FTicket);
end;
procedure TMTThread.Run;
begin
FStatusChange.Acquire;
try
if Status = tsInitializing then
CreateAndRun
else
if Status = tsWaiting then
FIntThread.Resume
else
raise EMTThreadError.CreateRes(@RsEThreadNotInitializedOrWaiting);
finally
FStatusChange.Release;
end;
end;
procedure TMTThread.SetName(const Value: string);
begin
FStatusChange.Acquire;
try
if Status in [tsInitializing, tsFinished] then
FName := Value
else
begin
if CurrentMTThread <> Self then
raise EMTThreadError.CreateRes(@RsECannotChangeNameOfOtherActiveThread);
FName := Value;
if FIntThread <> nil then
begin
FIntThread.Name := FName;
FIntThread.RaiseName;
end;
end;
finally
FStatusChange.Release;
end;
end;
procedure TMTThread.Synchronize(Method: TThreadMethod);
begin
if CurrentMTThread = Self then
FIntThread.Synchronize(Method)
else
if CurrentMTThread = nil then
Method
else
CurrentMTThread.Synchronize(Method);
end;
procedure TMTThread.Terminate;
begin
if Status in [tsTerminating, tsFinished] then
Exit;
FStatusChange.Acquire;
try
if FIntThread <> nil then
FIntThread.Terminate {thread was Running}
else
FFinished := True; {thread was initializing}
// make sure thread escapes from any Wait() calls
ReleaseSemaphore(FTerminateSignal, 1, nil);
finally
FStatusChange.Release;
end;
end;
procedure TMTThread.Wait;
var
SelfRef: TMTThread;
begin
if FManager.AcquireThread(Ticket, SelfRef) then
try
if GetCurrentThreadID = MainThreadID then
begin
while Status <> tsFinished do
begin
CheckSynchronize;
Sleep(1);
end;
end
else
begin
while Status <> tsFinished do
Sleep(1);
end;
finally
Release;
end;
end;
//=== { TMTManager } =========================================================
constructor TMTManager.Create;
begin
inherited Create;
FGenTicket := TCriticalSection.Create;
FThreadsChange := TCriticalSection.Create;
FThreads := TObjectList.Create(True);
end;
destructor TMTManager.Destroy;
var
I: Integer;
begin
// set the terminate flag at each thread
TerminateThreads;
// wait for them to finish
WaitThreads;
FThreadsChange.Acquire;
try
for I := 0 to FThreads.Count-1 do
Log('Unreleased thread: "' + TMTThread(FThreads[I]).Name + '"'); // do not localize
finally
FThreadsChange.Release;
end;
FThreads.Free;
FThreadsChange.Free;
FGenTicket.Free;
inherited Destroy;
end;
function TMTManager.AcquireNewThread: TMTThread;
begin
Result := TMTThread.Create(Self, GenerateTicket);
try
Result.IncRef;
FThreadsChange.Acquire;
try
FThreads.Add(Result);
finally
FThreadsChange.Release;
end;
except
Result.Free;
raise;
end;
end;
function TMTManager.AcquireThread(Ticket: TMTTicket; var Thread: TMTThread):
Boolean;
begin
FThreadsChange.Acquire;
try
Result := FindThread(Ticket, Thread);
if Result then
Thread.IncRef;
finally
FThreadsChange.Release;
end;
end;
// returns 0 = False
// 1 = True
// -1 = RaiseID found and active
function TMTManager.InternalActiveThreads(RaiseID: Longword): Integer;
var
I: Integer;
begin
Result := 0;
FThreadsChange.Acquire;
try
for I := 0 to FThreads.Count - 1 do
if TMTThread(FThreads[I]).Status <> tsFinished then
begin
if (RaiseID <> 0) and
(TMTThread(FThreads[I]).FIntThread.ThreadID = RaiseID) then
Result := -1
// no Break; here: Return -1 only when RaiseID is the last active thread
else
begin
Result := 1;
Break;
end;
end;
finally
FThreadsChange.Release;
end;
end;
function TMTManager.ActiveThreads: Boolean;
begin
Result := InternalActiveThreads(0) <> 0;
end;
function TMTManager.FindThread(Ticket: TMTTicket; var Thread: TMTThread):
Boolean;
var
I: Integer;
begin
FThreadsChange.Acquire;
try
I := FThreads.Count-1;
while (I <> -1) and (TMTThread(FThreads[I]).Ticket <> Ticket) do
Dec(I);
Result := I <> -1;
if Result then
Thread := TMTThread(FThreads[I])
else
Thread := nil;
finally
FThreadsChange.Release;
end;
end;
function TMTManager.GenerateTicket: TMTTicket;
begin
FGenTicket.Acquire;
try
Result := FNextTicket;
Inc(FNextTicket);
finally
FGenTicket.Release;
end;
end;
procedure TMTManager.Log(const Msg: string);
begin
// (rom) no OutputDebugString in production code
{$IFDEF DEBUGINFO_ON}
OutputDebugString(PChar('[' + ClassName + '] ' + Msg));
{$ENDIF DEBUGINFO_ON}
end;
procedure TMTManager.OnThreadFinished(Thread: TMTThread);
begin
TryRemoveThread(Thread);
end;
procedure TMTManager.ReleaseThread(Ticket: TMTTicket);
var
Thread: TMTThread;
begin
FThreadsChange.Acquire;
try
if FindThread(Ticket, Thread) then
Thread.DecRef
else
raise EMTThreadError.CreateRes(@RsEReleaseOfUnusedTicket);
// if this was the last reference then the thread must be removed
TryRemoveThread(Thread);
finally
FThreadsChange.Release;
end;
end;
procedure TMTManager.TerminateThreads;
var
I: Integer;
begin
FThreadsChange.Acquire;
try
for I := 0 to FThreads.Count-1 do
TMTThread(FThreads[I]).Terminate;
finally
FThreadsChange.Release;
end;
end;
procedure TMTManager.TryRemoveThread(Thread: TMTThread);
begin
FThreadsChange.Acquire;
try
if (Thread.Status = tsFinished) and (Thread.ReferenceCount = 0) then
FThreads.Remove(Thread);
finally
FThreadsChange.Release;
end;
end;
// wait until the threads are all finished
procedure TMTManager.WaitThreads;
begin
// running from inside the main VCL thread?
if GetCurrentThreadID = MainThreadID then
begin
// use CheckSynchronise to process the OnFinished events
while ActiveThreads do
begin
CheckSynchronize;
Sleep(1);
end;
end
else
begin
//running in a MTThread, just wait for all threads to finish
while True do
begin
case InternalActiveThreads(GetCurrentThreadID) of
0:
Break;
1:
{ Nothing };
-1:
raise EMTThreadError.CreateRes(@RsECurThreadIsPartOfManager);
end;
Sleep(1);
end;
end;
end;
{$IFDEF COMPILER5}
var
SyncWindowClass: TWndClass = (
style: 0;
lpfnWndProc: @SyncWndProc;
cbClsExtra: 0;
cbWndExtra: 0;
hInstance: 0;
hIcon: 0;
hCursor: 0;
hbrBackground: 0;
lpszMenuName: nil;
lpszClassName: 'JvMTThreadingSyncWindow');
procedure CreateSyncWindow;
begin
RegisterClass(SyncWindowClass);
SyncWindow := CreateWindowEx(WS_EX_TOOLWINDOW, SyncWindowClass.lpszClassName,
'', WS_POPUP, 0, 0, 0, 0, 0, 0, HInstance, nil);
end;
{$ENDIF COMPILER5}
initialization
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
RegisterUnitVersion(HInstance, UnitVersioning);
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
{$IFDEF COMPILER5}
InitializeCriticalSection(ThreadSyncLock);
CreateSyncWindow;
{$ENDIF COMPILER5}
finalization
{$IFDEF COMPILER5}
FinalizeSyncRequestList;
DeleteCriticalSection(ThreadSyncLock);
DestroyWindow(SyncWindow);
SyncWindow := 0;
{$ENDIF COMPILER5}
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
end.