我正在编写一个使用大量双向绑定的程序,使用的内存量已经成为一个巨大的问题。在我的完整应用程序中,我从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);
或者如果我直接在文本属性和宽度依赖属性之间绑定并使用BindingMode.OneWay,反之亦然。
使用CLR探查器似乎无法显示分配了什么,而且我没有访问商业内存探查器的权限。有人能向我解释一下内存中保存了什么,以及我如何在拥有连续绑定模式功能的同时摆脱它吗?我必须实现自己的绑定方法并自己处理事件吗?或者有什么东西我可以定期在GC之外冲洗?
谢谢你抽出时间。
使用这样的简单程序需要记住的一件事是,少量的代码最终可能会影响相当大的WPF基础设施。例如,对于单个TextBox
,您将使用布局引擎、属性引擎、模板系统、样式系统和可访问性功能。在您开始打字的那一刻,您还引入了输入系统、排版支持和一些非琐碎的国际化基础设施。
最后两个很可能是您在这个特定示例中看到的内容的很大一部分。WPF自动利用了许多OpenType字体功能,这需要它做大量的隐蔽工作。(碰巧的是,默认的UI字体实际上并没有起到多大作用,但你最终还是要为代码的输入付出代价,因为代码发现Segoe UI不是一个非常有趣的字体。)这是一个相对昂贵的功能,可以说明它的差异有多微妙。同样,令人惊讶的是,在支持区域设置的输入处理方面投入了大量精力——在完全支持i8n的情况下全面正确地处理输入比大多数人想象的要多。
您最终可能会为这些子系统中的每一个子系统付出代价,因为TextBox
并不是WPF中唯一使用它们的部分。因此,试图避免它们的手工构建解决方案所需的努力最终可能是徒劳的。
微小的测试应用程序描绘了一幅误导性的画面——在真实的应用程序中,支付的价格会更好地分摊。您的第一个TextBox
可能花费了30MB,但您现在已经分页了大量应用程序其他部分无论如何都要使用的内容。如果您从只使用ListBox
的应用程序开始,那么您可以添加TextBox
,并将内存消耗的差异与仅使用ListBox
的基线进行比较。这可能会让您对向应用程序添加TextBox
的边际成本有一个完全不同的了解。
因此,在一个琐碎的测试应用程序的上下文之外,编写自己的文本框所需的努力在实践中可能会对私有工作集产生非常小的影响。您最终肯定会对我在第一段中提到的所有功能和系统进行分页,因为TextBox
并不是WPF中唯一使用它们的东西。
这些系统中的每一个都能更节俭吗?毫无疑问,他们可以,但遗憾的是,WPF并没有像我希望的那样有太多的工程投入,除了Silverlight,更不用说有传言说Win8将再次尝试UI框架……不幸的是,高内存使用率是WPF的一个特点。(尽管要记住,WPF应用程序也会倾向于在内存更多的机器上使用更多的内存。在其工作集被驱动到最高效的水平之前,它需要一些内存压力。)
我真的很尴尬。。。我以为我已经检查了所有内容,并尝试了尽可能多的方法,但我不认为内存尖峰来自TextBox本身。这就是导致尖峰的原因。如果你删除了所有的绑定和绒毛,只让TextBox,即使它们的UndoLimit设置为零并限制MaxLength,在对TextBox的内容进行十几次编辑后,程序的内存仍然会激增15Mb+。因此,因为绑定还更新了Textbox,所以它们触发了这个峰值。我知道默认控件必须涵盖各种各样的用途,但作为我的第一个C#/WPF程序,我没有意识到在这种情况下它们实际上有多臃肿。我要去写我自己的TextBox控件,并提醒自己在这方面永远不要承担太多。但是嘿,至少我现在可以把我的自定义绑定代码放在一边了!