AvalonDock的DocumentContent未被垃圾回收

8

我创建了一个应用程序,使用了AvalonDock框架。其中一个关键部分是使用AvalonDock.DocumentContent派生的编辑器来编辑领域模型实体。我遇到了一个问题,发现我的编辑器在关闭并从DockingManager.Documents集合中删除后没有被垃圾回收。

经过一些无果的搜索后,我创建了一个小型测试应用程序,可以按照以下方式重新创建:

  • In Visual Studio (I'm using 2008), create a new WPF application called AvalonDockLeak;
  • Add a reference to the AvalonDock library (my version is 1.3.3571.0);
  • Add a new UserControl called Document;
  • Change Document.xmal to:

    <ad:DocumentContent x:Class="AvalonDockLeak.Document"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock">
        <Grid>
            <TextBox />
        </Grid>
    </ad:DocumentContent>
    
  • Change Document.xmal.cs to:

    namespace AvalonDockLeak
    {
        using AvalonDock;
    
        public partial class Document : DocumentContent
        {
            public Document()
            {
                InitializeComponent();
            }
    
            ~Document()
            {
            }
        }
    }
    

    The destructor I have added to be able to diagnose the problem adding a breakpoint on the methods opening {, and seeing if it gets hit. It always does on closing the test application but not earlier.

  • Now change Window1.xaml to:

    <Window x:Class="AvalonDockLeak.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
            Title="Memory Leak Test" Height="300" Width="300">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Button Name="NewButton" Click="NewButton_Click" Content="New" Height="26" Width="72" />
            <ad:DockingManager x:Name="DockManager" Grid.Row="1">
                <ad:DocumentPane />
            </ad:DockingManager>
        </Grid>
    </Window>
    
  • Change Window1.xaml.cs to:

    namespace AvalonDockLeak
    {
        using System.Windows;
    
        public partial class Window1 : Window
        {
            private int counter = 0;
    
            public Window1()
            {
                InitializeComponent();
            }
    
            private void NewButton_Click(object sender, RoutedEventArgs e)
            {
                string name = "Document" + (++this.counter).ToString();
                var document = new Document()
                {
                    Name = name,
                    Title = name,
                    IsFloatingAllowed = false
                };
    
                document.Show(this.DockManager);
                document.Activate();
            }
        }
    }
    

这个简单的应用程序也存在泄露。当关闭 DocumentContent 后,可以通过断点观察到打开 ~Document() 的大括号未被命中。

现在我想知道的是,这是一个已知问题吗?是否有方法来防止它?如果对象只有在很长时间后才被垃圾回收,那么我该怎么办才能加速这个过程?顺便说一下,调用 GC.Collect() 也没有帮助。


检查Avalon源代码,看看document.Show(this.DockManager);是做什么的。我猜这个文档以某种方式向管理器注册自己,并且没有被正确注销。在DockManager上是否有一种方法可以删除文档? - ChrisWue
它只是执行 manager.Documents.Add(this);。文档关闭后,它也不再存在于 manager.Documents 集合中。 - Wietze
好的,找出其中一种方法是附加一个内存分析器或创建一个内存转储,然后使用Windows调试工具来找出是什么在持有这些引用。 - ChrisWue
5个回答

4
显然,你的DocumentContent的引用被某个事件处理程序保留。你应该使用类似于CLR-Profiler的微软内存分析工具来确定原因。
你应该注意始终对已注册的事件进行反注册。否则可能会出现内存泄漏。为此,你可以使用 -= 运算符。

3
DocumentContent默认情况下关闭时会隐藏,这意味着引用仍然存在。
如果想让DocumentContent关闭并释放,需要在派生的DocumentContent中指定一些属性或修改AvalonDock源代码。
        this.IsCloseable = true;
        this.HideOnClose = false;

现在当关闭时,它会处理引用而不是像以前一样仅仅隐藏。

HideOnClose-属性在DocumentContent类中不存在(仅存在于DockableContent中)!我和楼主遇到了同样的问题。如果我调试派生的DocumentContent类,我可以看到当单击关闭符号时,会调用OnClosing()处理程序,但不会调用类的析构函数。那么如何关闭DocumentContent实例呢? - 0xDEADBEEF
@0xDEADBEEF 你是正确的,DocumentContent 没有包含 HideOnClose 属性。你是否尝试继续以编程方式添加 DocumentContent 实例,然后关闭它们,多次执行此操作,并通过内存分析器观察内存是否不断增长或者 GC 是否运行并回收内存?暂时忽略析构函数未被调用的问题。 - Aaron McIver
是的。我已经打开了50个实例。我可以在堆中找到它们。我关闭了它们并打开了100个新的实例。我做了两次,这250个实例都保留在堆中,内存不会减少。手动调用GC.Collect()也没有帮助。 - 0xDEADBEEF
最有可能的情况是DocumentContent存在于某个集合中,然而OP表示在关闭时manager.Documents不包含DocumentContent。让我看看能找到什么,会四处寻找。 - Aaron McIver

1
我在这个方向上也遇到了问题。关闭选项卡会导致内存泄漏。我使用分析器进行了检查,结果发现ActiveContent仍然会保留引用,阻止垃圾回收器的介入。
关闭选项卡的代码:
dc // DocumentContent, I want to close it
documentPane // DocumentPane, containing the dc

documentPane.Items.Remove(dc);

这个代码可以关闭标签页,但我发现还需要调用其他函数。

dc.Close();

在从documentPane中移除内容之前,如果我想将ActiveContent设置为null并让GC完成它的工作。

注意:我使用AvalonDock的1.2版本,这可能在更新的版本中有所改变。


1
我强烈建议您和任何使用AvalonDock 1.3的人升级到2.0版本。最新版本支持MVVM,不会出现这个问题(文档和锚点正确地进行垃圾回收)。更多信息:avalondock.codeplex.com
谢谢。

似乎从1.3升级到2.0没有简单的路径 - 例如,布局元素不再派生自FrameworkElement类。不过MVVM看起来不错 :-) - Dunc
不,这并不容易,但它解决了问题并且更多。@adospace 做得很好。 - Wietze

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接