在 TBaseVirtualTree.Destroy 函数里,有一段代码是这么写的:
if WasValidating then
begin
// Make sure we dequeue the two synchronized calls from ChangeTreeStatesAsync(), fixes mem leak and AV reported in issue #1001, but is more a workaround.
while CheckSynchronize() do
begin
Sleep(1);
end;
end;// if
注意这个 while 循环,因为它调用的 CheckSynchronize 去检查后台线程是不是异步调用了自己,但它没有考虑如果有其它线程或者同步代码在频繁 TThread.Synchronize 或 TThread.Queue/TThread.ForceQueue 时会产生什么影响,如果一旦发生这种情况,应用就可能会长时间阻塞在这块,无法正常响应,造成假死现象。
对于这一个问题的修复,我们可以通过调用计数的方式予以解决:
1、添加一个整形变量 FPendingSyncProcs,来记录本身调用 TThread.Synchronize 的次数
private
FPendingSyncProcs:Integer;//swish:记录下正在进行的异步调用,以保证退出时相关调用已经结束
2、每次同步函数处理时,减少这一计数
procedure TBaseVirtualTree.ChangeTreeStatesAsync(EnterStates, LeaveStates: TVirtualTreeStates);
begin
//TODO: If this works reliable, move to TWorkerThread
if not (csDestroying in ComponentState) then
begin
//swish:Increment invoke refs
AtomicIncrement(FPendingSyncProcs);
TThread.Synchronize(nil, procedure
begin
//Decrement invoke refs
AtomicDecrement(FPendingSyncProcs);
// Prevent invalid combination tsUseCache + tsValidationNeeded (#915)
if not ((tsUseCache in EnterStates) and (tsValidationNeeded in FStates + LeaveStates)) then
DoStateChange(EnterStates, LeaveStates);
if (tsValidating in FStates) and (tsValidating in LeaveStates) then
UpdateEditBounds();
end);
end;
end;
procedure TBaseVirtualTree.MeasureItemHeight(const Canvas: TCanvas; Node: PVirtualNode);
// If the height of the given node has not yet been measured then do it now.
var
NewNodeHeight: TDimension;
begin
if not (vsHeightMeasured in Node.States) then
begin
Include(Node.States, vsHeightMeasured);
if (toVariableNodeHeight in FOptions.MiscOptions) then
begin
NewNodeHeight := Node.NodeHeight;
// Anonymous methods help to make this thread safe easily.
if (MainThreadId <> GetCurrentThreadId) then
begin
//swish:Increment invoke refs
AtomicIncrement(FPendingSyncProcs);
TThread.Synchronize(nil,
procedure
begin
//swish:Decrement invoke refs
AtomicDecrement(FPendingSyncProcs);
DoMeasureItem(Canvas, Node, NewNodeHeight);
SetNodeHeight(Node, NewNodeHeight);
end
)
end
else
begin
DoMeasureItem(Canvas, Node, NewNodeHeight);
SetNodeHeight(Node, NewNodeHeight);
end;
end;
end;
end;
3、在上面的代码中,检查这个计数是否为0,如果为0,则不再循环。
if WasValidating then
begin
// Make sure we dequeue the two synchronized calls from ChangeTreeStatesAsync(), fixes mem leak and AV reported in issue #1001, but is more a workaround.
//swish:Add FPendingSyncProcs reference check avoid dead loop
while CheckSynchronize() and (FPendingSyncProcs>0) do
begin
Sleep(1);
end;
end;// if
至此,问题解决。