到目前为止,我的测试表明,在Silverlight中利用MVVM模式的所有标准方法,示例和框架都存在一个巨大的问题:大量内存泄漏会阻止VM被垃圾回收。
显然这是一个巨大且荒谬的主张 - 所以我的期望是有人会明白我为什么以及在哪里出错?:
重现的步骤很简单:
<TextBox Text="{Binding SomeText}" />
这将创建一个从根,BindingExpression扩展到viewmodel的引用链。然后,您可以从UI树中删除View以及所有对VM的引用 - 但是由于root&lt;&gt; BindingExpression&lt;&gt; VM引用链,VM永远不会被垃圾回收。
我创建了两个说明问题的例子。他们有一个按钮来创建一个新的视图/视图模型(它应该转储所有对旧视图的引用)和一个强制垃圾收集和报告当前内存使用情况的按钮。
示例1是超级剥离的校准微型示例。示例2不使用框架,只是以我能想到的最简单的方式说明问题。
对于那些可能想要帮助但又不想下载示例项目的人,这里是代码2的代码。我们从一个名为FooViewModel的viewmodel开始:
public class FooViewModel : INotifyPropertyChanged
{
string _fooText;
public string FooText
{
get { return _fooText; }
set
{
_fooText = value;
NotifyPropertyChanged("FooText");
}
}
private byte[] _data;
public FooViewModel()
{
_data = new byte[10485760]; //use up 10mb of memory
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
它只是暴露了一个名为FooText的字符串属性,我们也将绑定它。 INotifyPropertyChanged是促进绑定的必要条件。
然后我们有一个名为FooView的视图,它是一个包含以下内容的用户控件:
<UserControl x:Class="MVVMLeak.FooView">
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
<TextBlock Text="Bound textbox: " />
<TextBox Text="{Binding FooText}" Width="100"/>
</StackPanel>
</UserControl>
(为简洁省略了名称空间)
这里重要的一点是绑定到FooText属性的文本框。当然我们需要设置datacontext,我选择在代码隐藏中做,而不是引入ViewModelLocator:
public partial class FooView : UserControl
{
public FooView()
{
InitializeComponent();
this.DataContext = new FooViewModel();
}
}
MainPage看起来像这样:
<StackPanel x:Name="LayoutRoot" Background="White">
<Button Click="Button_Click" Content="Click for new FooView"/>
<Button Click="Button2_Click" Content="Click to garbage collect"/>
<ContentControl x:Name="myContent"></ContentControl>
</StackPanel>
后面的代码中包含以下内容:
private void Button_Click(object sender, RoutedEventArgs e)
{
myContent.Content = new FooView();
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Memory in use after collection: " + (GC.GetTotalMemory(true) / 1024 / 1024).ToString() + "MB");
}
注意:要复制问题,请务必在文本框中键入内容,因为我相信在需要之前不会创建绑定表达式。
值得注意的是this KB article可能是相关的,但是我不相信,因为'方法2'解决方法似乎没有效果,并且参考链似乎不匹配。
此外,我不确定这是否重要,但我使用CLR Profiler来诊断原因。
更新
如果有人想测试,并在评论中报告他们的发现,我将通过Dropbox托管silverlight应用程序:Hosted Example。重现:点击顶部按钮,键入内容,按顶部按钮,键入内容,按顶部按钮。然后按下按钮。如果它报告10MB的使用量(或者可能是其他一些未增加的量),则表示您没有遇到内存泄漏。
到目前为止,问题似乎发生在我们所有的开发机器上,它们是ThinkPad w510(43192RU),配备12GB Ram,64位Win 7 Enterprise。这包括一些没有安装开发工具的。值得注意的是,他们正在运行VMWare工作站。
在我尝试的其他机器上似乎没有出现这个问题 - 包括办公室中的几台家用电脑和其他电脑。我们在某种程度上排除了SL版本,内存量以及vmware。仍然没有找到原因。
答案 0 :(得分:2)
尚未找到解决方案,但现在已确定问题。如果由于以下原因而调用Silverlights的自动化系统,则会发生此行为:
因此出现了一个新问题:我们如何禁用自动化操作员或以其他方式正确清理它们?
这篇文章说明了一种方法:WPF UserControl Memory leak
然而,它并不是一个真正可行的解决方案,因为我们必须覆盖我们计划使用绑定的每个silverlight控件,更不用说复杂控件的控件模板了。
如果有人能找到一个好的解决方案,我会改变我的答案,但现在似乎没有一个......
修改:
这是一个很好的小解决方案,似乎可以完成这项工作。只需在定义silverlight对象的HTML中添加以下参数:
<param name="windowless" value="true" />
在“无窗口”模式下运行的副作用是自动化不起作用:)
答案 1 :(得分:0)
第二个例子中没有内存泄漏。
使用FooView
影响ContentControl
myContent.Content = new FooView();
新实例后,不再使用引用到整个View + ViewModel对象图。
最终会在需要时进行垃圾收集。
也许你应该澄清一下你认为存在内存泄漏的原因(即统计数据,重复步骤等等)。