C# 中带有 IExpandCollapseProvider 的自定义 FrameWorkElementAutomationPeer 的内存泄漏

Memory leak with custom FrameWorkElementAutomationPeer with IExpandCollapseProvider in C#

我们使用 AutomationUIClient 控制台应用程序来测试我们的 WPF 应用程序。

我们在 WPF 应用程序中设置了自定义 class TreeItemAutomationPeer with ISelectionProvider ad IExpandCollapseProvider

如果自动化应用程序控制台使用此对象,则所有者(在我的例子中是 TreeItem)会保留,因此它会泄漏...

我们添加了最后一个方法 GetChildrenCore() 以防止 Children 的内存泄漏。

    public class TreeItemAutomationPeer : FrameworkElementAutomationPeer, ISelectionItemProvider, IExpandCollapseProvider
{
    private readonly TreeItem _treeItem;

    public TreeItemAutomationPeer(TreeItem treeItem)
        : base(treeItem)
    {
        _treeItem = treeItem;
    }

    public override object GetPattern(PatternInterface patternInterface)
    {
        if (patternInterface == PatternInterface.SelectionItem ||
            patternInterface == PatternInterface.ExpandCollapse)
            return this;
        return base.GetPattern(patternInterface);
    }

    protected override string GetClassNameCore()
    {
        return "TreeItem";
    }

    protected override AutomationControlType GetAutomationControlTypeCore()
    {
        //return AutomationControlType.Tree;
        return AutomationControlType.Custom;
    }

    #region ISelectionItemProvider
    public IRawElementProviderSimple SelectionContainer
    {
        get { return _treeItem.SelectionContainer; }
    }
    public bool IsSelected { get { return _treeItem.Item.IsSelected; } }
    public void AddToSelection()
    {
        _treeItem.Item.IsSelected = true;
        ItemHelper.SelectItem(_treeItem, _treeItem.Item);

    }
    public void RemoveFromSelection()
    {
        _treeItem.Item.IsSelected = false;
    }
    public void Select()
    {
        if (_treeItem.Item.IsSelected)
            RemoveFromSelection();
        else AddToSelection();
    }
    #endregion

    #region IExpandCollapseProvider

    public ExpandCollapseState ExpandCollapseState
    {
        get
        {
            return _treeItem.Item.IsExpanded
                ? ExpandCollapseState.Expanded
                : ExpandCollapseState.Collapsed;
        }
    }

    public void Expand()
    {
        _treeItem.Item.IsExpanded = true;
    }

    public void Collapse()
    {
        _treeItem.Item.IsExpanded = false;
    }

    #endregion
    protected override List<AutomationPeer> GetChildrenCore()
    {
        return null;
    }

}

TreeItem class 实现 IRawElementProviderSimple

        #region Automation

    private TreeItemAutomationPeer _itemAutomationPeer;
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        _itemAutomationPeer = new TreeItemAutomationPeer(this);
        return _itemAutomationPeer;
    }

    public IRawElementProviderSimple SelectionContainer
    {
        get { return _container; }
    }

    #endregion

    #region IRawElementProviderSimple

    protected IntPtr GetWindowHandle() { return IntPtr.Zero; }
    protected string GetName() { return Name; }
    protected void AddAutomationProperty(int propertyId, object value) { }
    public object GetPatternProvider(int patternId) { return null; }
    public object GetPropertyValue(int propertyId)
    {
        return propertyId == AutomationElementIdentifiers.NameProperty.Id ? GetName() : null;
    }
    public IRawElementProviderSimple HostRawElementProvider { get { return null; } }
    public ProviderOptions ProviderOptions
    {
        get { return ProviderOptions.ServerSideProvider; }
    }

    #endregion

这是来自 DotMemory 的泄漏:

当我拍摄快照时,控制台应用程序仍附加到 WPF 应用程序。

如何从 ExpandCollapseProviderWrapper 释放 TreeItemAutomationPeer 的所有者以防止泄漏?

在控制台应用程序中,我们获取 AutomationElement 对象。是否有我们使用的所有 AutomationElement 的列表以及释放它们的方法?

非常感谢:)

虽然真实

编辑:

根据 dotmemory 文档 (https://www.jetbrains.com/help/dotmemory/Analyzing_GC_Roots.html),RefCounted 句柄是:

The root prevents garbage collection if the reference count of the object is a certain value. If an object is passed to a COM library using COM Interop, CLR creates a RefCounted handle to this object. This root is needed as COM is unable to perform garbage collection. Instead, it uses reference counting. If the object is no longer needed, COM sets the count to 0. This means that RefCounted handle is no longer a root and the object can be collected. Thus, if you see RefCounted handle, then, probably, the object is passed as an argument to unmanaged code.

我找到了解决方法! :)

在控制台应用程序中,我使用 System.Diagnostics.Process.Start() 来启动我的应用程序。

由于 TreeItemAutomationPeer 由 COM 持有,我尝试从进程“分离”,调用 GC 并重新附加到进程。

 var processId = process.Id;
        process = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        process = System.Diagnostics.Process.GetProcessById(processId);

在我的 dotMemory 快照中不再有泄漏 \o/