使用DependencyProperty的最简单的WPF UserControl中的内存泄漏

时间:2017-10-30 13:53:09

标签: c# wpf memory-leaks dependency-properties

所以我试图创建一个具有一个新依赖项属性并使用此DP的WPF控件。控件的XAML就像这样简单:

<UserControl x:Class="DPTest.TestControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:DPTest">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=local:TestControl}, Path=TestText}"/>

只是显示新属性的TextBlock。 Codebehind声明了依赖属性,如下所示:

using System.Windows;

namespace DPTest
{
  public partial class TestControl
  {
    public static readonly DependencyProperty TestTextProperty = 
      DependencyProperty.Register("TestText", typeof(string), typeof(TestControl), new PropertyMetadata(default(string)));

    public TestControl()
    {
      InitializeComponent();
    }

    public string TestText
    {
      get => (string) GetValue(TestTextProperty);
      set => SetValue(TestTextProperty, value);
    }
  }
}

到目前为止一切都很好。但是,创建此控件永远不会被GC收集。为了证明这一点,我使用了一个Window,只需在加载时创建大量的TestControl:

using System.Windows;

namespace DPTest
{
  public partial class MainWindow
  {
    public MainWindow()
    {
      InitializeComponent();
      Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
      while (true)
      {
        var uc = new TestControl();
      }
    }
  }
}

打开此窗口会显示RAM快速增加,而GC会频繁启动。内存分析显示位于RAM中的TestControl的所有实例。

如果我用常量值替换控件内的绑定,那么就像这样:

<TextBlock Text="hello"/>

RAM不会增加,并且会成功收集未使用的控件实例。

看起来我做错了但是这个例子非常简单和常见,所以我卡住了。请帮忙。

2 个答案:

答案 0 :(得分:2)

您正在控制调度程序线程,这会阻止数据绑定引擎执行任何操作。如果我冒险猜测,有些东西正在排队等待由DataBind调度程序优先级的数据绑定引擎处理,而你的循环正在阻止这种情况发生。

如果你像这样重写它,你仍然会生成无数的测试控件,但你不会看到你的记忆增长:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    InstantiateNewControl();
}

private void InstantiateNewControl()
{
    var tc = new TestControl();
    this.Dispatcher.BeginInvoke(
        DispatcherPriority.Background,
        new Action(InstantiateNewControl));
}

这将为数据绑定引擎提供在每个实例化之间完成一些工作的机会。

如果您注释掉指定DispatcherPriority.Background的行,您会看到内存再次开始增长。这是因为默认优先级是最高的(Normal),它优先于布局,输入,数据绑定等。通过以高优先级充斥调度程序,您将阻止其他工作完成。

更新

在使用内存分析器进行调查后,看起来就像内存增长源于BindingExpression中保持活动的新DataBindManager实例。具体来说,有一个字典将绑定表达式映射到它们最近的数据绑定操作,看起来你正在阻止该字典被正确维护。新实例化的测试控件上的表达式永远不会被删除。

答案 1 :(得分:0)

这与你的控制毫无关系。你的while循环 在加载的事件中永远运行。垃圾收集器根本无法收集 和创建新控件一样快。

尝试使用foreach循环和几千次迭代 - 至少应该让你开始运行