mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
236 lines
6.9 KiB
ObjectPascal
236 lines
6.9 KiB
ObjectPascal
unit VirtualTrees.WorkerThread;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
VirtualTrees;
|
|
|
|
type
|
|
// internal worker thread
|
|
TWorkerThread = class(TThread)
|
|
private
|
|
FCurrentTree: TBaseVirtualTree;
|
|
FWaiterList: TThreadList;
|
|
FRefCount: Integer;
|
|
FWorkEvent: THandle;
|
|
class procedure EnsureCreated();
|
|
class procedure Dispose(CanBlock: Boolean);
|
|
procedure WaitForValidationTermination(Tree: TBaseVirtualTree);
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
constructor Create();
|
|
destructor Destroy; override;
|
|
|
|
/// For lifeteime management of the TWorkerThread
|
|
class procedure AddThreadReference;
|
|
class procedure ReleaseThreadReference(ACanBlock: Boolean = False);
|
|
|
|
class procedure AddTree(Tree: TBaseVirtualTree);
|
|
class procedure RemoveTree(pTree: TBaseVirtualTree; pWaitForValidationTermination: Boolean);
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.Windows,
|
|
System.Types,
|
|
System.SysUtils;
|
|
|
|
type
|
|
TBaseVirtualTreeCracker = class(TBaseVirtualTree)
|
|
end;
|
|
|
|
var
|
|
WorkerThread: TWorkerThread = nil;
|
|
//----------------- TWorkerThread --------------------------------------------------------------------------------------
|
|
|
|
class procedure TWorkerThread.EnsureCreated();
|
|
begin
|
|
if not Assigned(WorkerThread) then
|
|
// Create worker thread, initialize it and send it to its wait loop.
|
|
WorkerThread := TWorkerThread.Create();
|
|
end;
|
|
|
|
class procedure TWorkerThread.Dispose(CanBlock: Boolean);
|
|
var
|
|
LRef: TThread;
|
|
begin
|
|
WorkerThread.FreeOnTerminate := not CanBlock;
|
|
WorkerThread.Terminate();
|
|
SetEvent(WorkerThread.FWorkEvent);
|
|
LRef := WorkerThread;
|
|
WorkerThread := nil; //Will be freed usinf TThread.FreeOnTerminate
|
|
if CanBlock then
|
|
LRef.Free;
|
|
end;
|
|
|
|
|
|
class procedure TWorkerThread.AddThreadReference;
|
|
begin
|
|
TWorkerThread.EnsureCreated();
|
|
InterlockedIncrement(WorkerThread.FRefCount);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
class procedure TWorkerThread.ReleaseThreadReference(ACanBlock: Boolean);
|
|
begin
|
|
if Assigned(WorkerThread) then
|
|
begin
|
|
if InterlockedDecrement(WorkerThread.FRefCount) = 0 then
|
|
begin
|
|
WorkerThread.Dispose(ACanBlock);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
constructor TWorkerThread.Create();
|
|
|
|
begin
|
|
FWaiterList := TThreadList.Create;
|
|
// Create an event used to trigger our worker thread when something is to do.
|
|
FWorkEvent := CreateEvent(nil, False, False, nil);
|
|
if FWorkEvent = 0 then
|
|
RaiseLastOSError;
|
|
inherited Create(False);
|
|
FreeOnTerminate := True;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
destructor TWorkerThread.Destroy;
|
|
|
|
begin
|
|
// First let the ancestor stop the thread before freeing our resources.
|
|
inherited;
|
|
CloseHandle(FWorkEvent);
|
|
FWaiterList.Free;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TWorkerThread.WaitForValidationTermination(Tree: TBaseVirtualTree);
|
|
begin
|
|
// Wait for any references to this tree to be released.
|
|
while FCurrentTree = Tree do
|
|
begin
|
|
Sleep(1); // Don't do busy waiting, let the OS scheduler give other threads a time slice
|
|
CheckSynchronize(); // We need to call CheckSynchronize here because we are using TThread.Synchronize in TBaseVirtualTree.MeasureItemHeight() and ChangeTreeStatesAsync()
|
|
end;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
procedure TWorkerThread.Execute();
|
|
|
|
// Does some background tasks, like validating tree caches.
|
|
|
|
var
|
|
EnterStates: TVirtualTreeStates;
|
|
lExceptAddr: Pointer;
|
|
lException: TObject;
|
|
lCurrentTree: TBaseVirtualTree;
|
|
begin
|
|
TThread.NameThreadForDebugging('VirtualTrees.TWorkerThread');
|
|
while not Terminated do
|
|
try
|
|
WaitForSingleObject(FWorkEvent, INFINITE);
|
|
if Terminated then
|
|
exit;
|
|
|
|
// Get the next waiting tree.
|
|
with FWaiterList.LockList do
|
|
try
|
|
if Count > 0 then
|
|
begin
|
|
lCurrentTree := Items[0];
|
|
// Remove this tree from waiter list.
|
|
Delete(0);
|
|
// If there is yet another tree to work on then set the work event to keep looping.
|
|
if Count > 0 then
|
|
SetEvent(FWorkEvent);
|
|
end
|
|
else
|
|
lCurrentTree := nil;
|
|
finally
|
|
FWaiterList.UnlockList;
|
|
end;
|
|
|
|
// Something to do?
|
|
if Assigned(lCurrentTree) then
|
|
begin
|
|
try
|
|
TBaseVirtualTreeCracker(lCurrentTree).ChangeTreeStatesAsync([tsValidating], [tsUseCache, tsValidationNeeded]);
|
|
FCurrentTree := lCurrentTree;
|
|
EnterStates := [];
|
|
if not (tsStopValidation in FCurrentTree.TreeStates) and TBaseVirtualTreeCracker(FCurrentTree).DoValidateCache then
|
|
EnterStates := [tsUseCache];
|
|
finally
|
|
FCurrentTree := nil; // Important: Clear variable before calling ChangeTreeStatesAsync() to prevent deadlock in WaitForValidationTermination(). See issue #1001
|
|
TBaseVirtualTreeCracker(lCurrentTree).ChangeTreeStatesAsync(EnterStates, [tsValidating, tsStopValidation]);
|
|
end;
|
|
end;
|
|
except
|
|
on Exception do
|
|
begin
|
|
lExceptAddr := ExceptAddr;
|
|
lException := AcquireExceptionObject;
|
|
TThread.Synchronize(nil, procedure
|
|
begin
|
|
raise lException at lExceptAddr;
|
|
end);
|
|
Continue; //the thread should continue to run
|
|
end;
|
|
end;//while
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
class procedure TWorkerThread.AddTree(Tree: TBaseVirtualTree);
|
|
|
|
begin
|
|
Assert(Assigned(Tree), 'Tree must not be nil.');
|
|
TWorkerThread.EnsureCreated();
|
|
|
|
// Remove validation stop flag, just in case it is still set.
|
|
TBaseVirtualTreeCracker(Tree).DoStateChange([], [tsStopValidation]);
|
|
with WorkerThread.FWaiterList.LockList do
|
|
try
|
|
if IndexOf(Tree) = -1 then
|
|
Add(Tree);
|
|
finally
|
|
WorkerThread.FWaiterList.UnlockList;
|
|
end;
|
|
|
|
SetEvent(WorkerThread.FWorkEvent);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
class procedure TWorkerThread.RemoveTree(pTree: TBaseVirtualTree; pWaitForValidationTermination: Boolean);
|
|
begin
|
|
if not Assigned(WorkerThread) then
|
|
exit;
|
|
Assert(Assigned(pTree), 'pTree must not be nil.');
|
|
|
|
with WorkerThread.FWaiterList.LockList do
|
|
try
|
|
Remove(pTree);
|
|
finally
|
|
WorkerThread.FWaiterList.UnlockList; // Seen several AVs in this line, was called from TWorkerThrea.Destroy. Joachim Marder.
|
|
end;
|
|
if pWaitForValidationTermination then
|
|
WorkerThread.WaitForValidationTermination(pTree);
|
|
end;
|
|
|
|
|
|
end.
|