WPF内存分配使用绑定,inotifypropertychanged和依赖属性跳跃

时间:2011-06-29 16:02:16

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

我正在编写一个使用大量双向绑定的程序,并且使用的内存量已成为一个巨大的问题。在我的完整应用程序中,我从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之外冲洗吗?

感谢您的时间。

2 个答案:

答案 0 :(得分:2)

使用这样的简单程序要记住的一件事是,少量代码最终会占用相当大量的WPF基础架构。例如,使用单个TextBox您正在使用布局引擎,属性引擎,模板系统,样式系统和辅助功能。您开始输入的那一刻,您还会引入输入系统,排版支持和一些非平凡的国际化基础设施。

最后两个可能是你在这个特定例子中所看到的很大一部分。 WPF自动利用了许多OpenType字体功能,这要求它在幕后做很多工作。 (实际上,默认的UI字体实际上并没有对它做很多事情,但你仍然最终为代码发现Segoe UI不是一个非常有趣的字体的代码的价格。)这是一个相对昂贵的功能说它有多么微妙的区别。同样,令人惊讶的是,对于区域设置感知的输入处理有多大意义 - 在完全支持i8n的情况下全面正确地完成这项工作比大多数人想象的更多。

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

微小的测试应用程序描绘了一种误导性的图片 - 在实际应用中,所支付的价格分享得更好一些。您的第一个TextBox可能已经花费了30MB,但您现在已经分页了一大堆其他应用程序将要使用的内容。如果您开始使用只使用ListBox的应用程序,则可以添加TextBox并将内存消耗的差异与ListBox - 仅基线进行比较。这可能会让您对应用程序中添加TextBox的边际成本有相当不同的描述。

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

这些系统中的每一个都可以更节俭吗?毫无疑问,他们可以,但遗憾的是,WPF没有像我想的那样多的工程输入,Silverlight的分散注意力,更不用说有关UI框架的另一次尝试即将在Win8中出现的传闻。不幸的是,高内存使用是WPF的一个特性。 (尽管请记住,WPF应用程序也会在具有更多内存的计算机上使用更多内存。在其工作集被驱动到最有效的级别之前需要一些内存压力。)

答案 1 :(得分:0)

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