【问题标题】:WPF memory allocation leaps using binding, inotifypropertychanged and dependency propertyWPF 内存分配使用绑定、inotifypropertychanged 和依赖属性实现飞跃
【发布时间】:2011-09-25 07:18:24
【问题描述】:

我正在编写一个使用一堆双向绑定的程序,使用的内存量已成为一个大问题。在我的完整应用程序中,我从 50Mb 开始,然后,仅通过使用绑定(即更改一侧的值并让绑定更新另一侧),我通常会打破 100Mb,即使我的代码没有分配任何新内容.我的问题是这个额外的内存是什么以及我可以做些什么来控制它。我在下面创建了一个简单、可重现的示例:

假设我有一个包含以下内容的主窗口:

<StackPanel Height="25" Orientation="Horizontal">
    <TextBox UndoLimit="1" Name="TestWidth" />
    <Label>,</Label>
    <TextBox UndoLimit="1" Name="TestHeight" />
</StackPanel>

然后在这个窗口的构造函数中,我生成一个新窗口,显示它,然后将它的 WidthProperty 和 HeightProperty 依赖属性绑定到利用 INotifyPropertyChanged 的​​变量:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private int _WidthInt;
    public int WidthInt
    {
        get { return _WidthInt; }
        set { _WidthInt = value; NotifyPropertyChanged("WidthInt"); }
    }

    private int _HeightInt;
    public int HeightInt
    {
        get { return _HeightInt; }
        set { _HeightInt = value; NotifyPropertyChanged("HeightInt"); }
    }

    public MainWindow()
    {
        InitializeComponent();
        Window testWindow = new Window();
        testWindow.Show();

        Binding bind = new Binding("HeightInt");
        bind.Source = this;

        bind.Mode = BindingMode.TwoWay;
        testWindow.SetBinding(Window.HeightProperty, bind);
        //bind.Converter = new convert();
        //this.TestHeight.SetBinding(TextBox.TextProperty, bind);

        bind = new Binding("WidthInt");
        bind.Source = this;

        bind.Mode = BindingMode.TwoWay;
        testWindow.SetBinding(Window.WidthProperty, bind);
        //bind.Converter = new convert();
        //this.TestWidth.SetBinding(TextBox.TextProperty, bind);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string sProp)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(sProp));
            GC.Collect();
        }
    }

然后,如果我不断调整窗口大小,我在任务管理器中的内存使用量线性增加,没有明显的上限。该程序从 17Mb 开始,在调整大小后 30 秒内增加到 20Mb 并在 20 的某个点后悬停(感谢 Ian)。即使没有绑定,这实际上也会发生,并且内存不会回退。虽然很烦人,但这不是我所说的“内存飞跃”。

如果我取消注释也将文本框绑定到变量的行,我会得到以下结果:在短短几秒钟内,它从 18Mb 跳转到 38Mb,然后悬停在那里(请注意在 XAML 中设置文本框的绑定不影响内存尖峰)。我尝试为文本框绑定实现自己的转换器,但这不会影响内存使用。

如果我将变量更改为新的依赖属性并绑定到它们,跳转仍然存在,例如

    public static readonly DependencyProperty WidthIntProperty = DependencyProperty.Register("WidthIntProperty", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0, null));
    int WidthInt
    {
        get { return (int)this.GetValue(WidthIntProperty); }
        set { this.SetValue(WidthIntProperty, value); }
    }
...
        Binding bind = new Binding("Text");
        bind.Source = TestHeight;
        bind.Mode = BindingMode.TwoWay;
        this.SetBinding(MainWindow.HeightIntProperty, bind);
        testWindow.SetBinding(Window.HeightProperty, bind);

或者如果我直接在 text 属性和 width 依赖属性之间绑定并使用 BindingMode.OneWay 或反之亦然。

使用 CLR 分析器似乎无法向我显示正在分配的内容,而且我无权访问商业内存分析器。有人可以向我解释一下内存中保存了什么以及如何在仍然具有连续 BindingMode 的功能的同时摆脱它吗?我是否必须实现自己的绑定方法并自己处理事件?或者有什么我可以定期在 GC 之外冲洗的吗?

感谢您的宝贵时间。

【问题讨论】:

  • 在 GC.Collect 未注释的情况下,我无法重现您第一个场景的“继续”部分。在调试器之外运行 Release 构建,它达到大约 24,700K,然后在此附近波动,但不会超过该值。同样,您的第二个场景(绑定文本框)也稳定下来。离开 GC.Collect 注释掉,它仍然稳定,只是在一个更高的水平。 (我的系统上大约有 34MB。)我没有看到持续增加。必须缺少一些细节才能重现这一点。您是在运行发布还是调试?
  • 感谢您尝试重现我的结果,Ian。 (编辑:ack,我没有意识到输入自动提交而不是添加新行...)我在 Visual C# 中运行调试,doh!在取消注释 GC.Collect 并在环境之外运行发布后,它从 15.5Mb 开始并稳定在 17.5Mb,即使没有绑定 (!) 也会发生这种情况。如果我取消注释也绑定 TextBoxes 的行,我会从 15.8Mb 开始,它实际上确实达到了 33.7Mb!这显然不是内存泄漏,但我仍然对所有这些额外内存有疑问。我会更新问题以反映这一点。

标签: wpf data-binding memory-leaks dependency-properties inotifypropertychanged


【解决方案1】:

像这样的简单程序需要记住的一点是,少量代码最终可能会影响相当大量的 WPF 基础架构。例如,对于单个 TextBox,您正在使用布局引擎、属性引擎、模板系统、样式系统和可访问性功能。在您开始打字的那一刻,您还引入了输入系统、排版支持和一些重要的国际化基础设施。

最后两个很可能是您在这个特定示例中看到的大部分内容。 WPF 自动利用了很多 OpenType 字体功能,这需要它在后台做很多工作。 (碰巧默认的 UI 字体实际上并没有用它做很多事情,但你最终还是要为发现 Segoe UI 不是一个非常有趣的字体的代码付出代价。)对于说它产生了多么微妙的差异。同样,区域感知输入处理投入了如此之多的工作量令人惊讶——在完全支持 i8n 的情况下全面正确地完成这项工作比大多数人想象的要多。

您最终可能会为这些子系统中的每一个付出代价,因为TextBox 并不是 WPF 中唯一使用它们的部分。因此,试图避免它们的手工构建解决方案所需的努力最终可能是徒劳的。

微小的测试应用程序描绘了一幅误导性的画面 - 在实际应用程序中,所付出的代价会更好地分摊。您的第一个TextBox 可能花费了您的 30MB,但您现在已经分页加载了应用程序的其余部分无论如何都会使用的内容。如果您从一个只使用ListBox 的应用程序开始,那么您可以添加一个TextBox 并将内存消耗的差异与ListBox-only 基线进行比较。这可能会让您对在应用程序中添加 TextBox 的边际成本有一个完全不同的了解。

因此,在琐碎的测试应用程序的上下文之外,编写自己的文本框所需的工作可能在实践中对私有工作集产生很小的影响。几乎可以肯定,您最终会在第一段中提到的所有功能和系统中分页,因为 TextBox 并不是 WPF 中唯一使用它们的东西。

这些系统可以更节俭吗?毫无疑问,他们可以,但遗憾的是,WPF 并没有我想要的那么多的工程投入,Silverlight 会让人分心,更不用说在 Win8 中再次尝试 UI 框架的传言了。 . 不幸的是,高内存使用率是 WPF 的一个特性。 (但请记住,WPF 应用程序也倾向于在具有更多内存的机器上使用更多内存。在其工作集被驱动到最有效的水平之前,它需要一些内存压力。)

【讨论】:

  • 感谢您详细解释幕后情况。既然是这种情况,我正在使用其他默认的 UIElements 可能会分页相同的功能,自定义文本框必须是许多控件中的第一个,然后我不得不问自己,“这是我应该使用的平台吗?正在研究是否需要轻量级的程序?”
  • WPF 有许多好的特性,但遗憾的是,轻量级并不是其中之一。您需要清楚了解您的典型最终用户系统,以了解 WPF 是否适合您。
【解决方案2】:

我真的很尴尬...我以为我已经检查了所有内容并尝试了尽可能多的方法,但我不认为 内存峰值来自 TextBox 本身。这就是导致峰值的原因。如果您删除所有绑定和绒毛,只保留文本框,即使将它们的 UndoLimit 设置为零并限制 MaxLength,在对文本框进行十多次编辑后,程序的内存仍会激增 15Mb+内容。所以因为绑定也更新了文本框,所以它们触发了这个峰值。我知道默认控件必须涵盖多种用途,但作为我的第一个 C#/WPF 程序,我没有意识到在这种情况下它们实际上是多么臃肿。我要编写自己的 TextBox 控件,并提醒自己在这方面永远不要假设太多。但是,嘿,至少我现在可以把我的自定义绑定代码放在一边了!

【讨论】:

    猜你喜欢
    • 2011-04-02
    • 2012-11-08
    • 2014-08-31
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 2013-08-12
    • 2011-06-08
    • 1970-01-01
    相关资源
    最近更新 更多