[教程]VirtualTreeView 组件中,释放时可能造成假死问题修复

在 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

至此,问题解决。

分享到: