Silverlight + MVVM + 绑定 = 内存泄漏?

Posted

技术标签:

【中文标题】Silverlight + MVVM + 绑定 = 内存泄漏?【英文标题】:Silverlight + MVVM + Bindings = Memory leaks? 【发布时间】:2012-05-25 12:53:34 【问题描述】:

到目前为止,我的测试表明,在 silverlight 中利用 MVVM 模式的所有标准方法、示例和框架都存在一个巨大的问题:大量内存泄漏会阻止 VM 被垃圾收集。

显然这是一个巨大而荒谬的说法 - 所以我的期望是有人会对我为什么以及哪里出错有一个明显的答案:)

重现的步骤很简单:

通过将视图数据上下文设置到 VM 将视图模型绑定到视图(假设视图模型利用 INotifyPropertyChanged 来支持数据绑定) 将 UI 元素绑定到视图模型上的属性,例如:

<TextBox Text="Binding SomeText" />

以某种方式利用绑定(例如 - 只需在文本框中输入)。

这将创建一个从根延伸到 BindingExpression 的引用链,再到您的视图模型。然后,您可以从 UI 树中删除视图,以及对 VM 的所有引用 - 但是由于 rootBindingExpressionVM 引用链,VM 永远不会被垃圾收集。

我创建了两个示例来说明问题。他们有一个按钮来创建一个新的视图/视图模型(它应该转储所有对旧视图的引用)和一个强制垃圾收集和报告当前内存使用情况的按钮。

示例 1 是一个超级精简的 caliburn 微示例。示例 2 没有使用任何框架,只是以我能想到的最简单的方式来说明问题。

Example 1

Example 2

对于那些可能希望提供帮助但不想下载示例项目的人,这里是示例 2 的代码。我们从一个名为 FooViewModel 的视图模型开始:

 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 属性的文本框。当然,我们需要设置数据上下文,我选择在代码隐藏中执行此操作,而不是引入 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 内存,64 位 Win 7 Enterprise。这包括一些没有安装开发工具的。值得注意的是,他们正在运行 VMWare 工作站。

我尝试过的其他机器上似乎没有出现此问题 - 包括几台家用电脑和办公室中的其他电脑。我们在一定程度上排除了 SL 版本、内存量,可能还有 vmware。还没找到原因。

【问题讨论】:

旁注:caliburn micro(以及我目前的方法)的一个非常丑陋/糟糕的解决方法是在 OnDeactivate(true) 覆盖中将所有私有变量设置为 null。至少那时我们泄漏得更慢了,但这充其量只是一个创可贴。 如果泄漏只在运行时出现,你应该看看这个:***.com/questions/8544458/… 我没有看过那个样本,但我猜这是同一个问题。 我运行了示例 1,没有泄漏。无论我点击多少次“new FooView”,内存都保持在 10MB。 我的环境是W7、IE9、SL5。我已经在针对 SL4 和 SL5 的 VS2010 和 VS11 beta 中对其进行了测试。内存在开始时增长了一点(300Bytes),但之后它是恒定的 【参考方案1】:

在您的第二个示例中没有内存泄漏。

使用myContent.Content = new FooView(); 将新的FooView 实例影响到ContentControl 后,整个View + ViewModel 对象图不再使用引用

最终会在需要时进行垃圾收集。

也许您应该澄清是什么让您认为存在内存泄漏(即统计信息、重现步骤...等)

【讨论】:

正如更新中提到的,泄漏似乎是特定于某些机器(可能是一小部分机器)的 PC 和/或环境。我同意不应该有内存泄漏,但肯定有。由于到目前为止这个问题只出现在我们的 thinkpad w510 电脑上,所以我暂时提出了这个问题,因为我已经没有想法了。【参考方案2】:

尚未找到解决方案,但问题现已确定。如果由于以下原因调用 Silverlights 的自动化功能,则会发生此行为:

平板电脑输入服务(换句话说,所有“类似平板电脑”的电脑) 自动化测试工具 屏幕阅读器(和其他辅助功能软件)

更多信息在这里:http://www.wintellect.com/cs/blogs/sloscialo/archive/2011/04/13/silverlight-memory-leaks-and-automationpeers.aspx

因此出现了一个新问题:我们如何禁用自动化节点或以其他方式让它们正确清理?

这篇文章说明了一种方法:WPF UserControl Memory leak

但是,这并不是一个真正可行的解决方案,因为我们必须覆盖我们计划使用绑定的每个 silverlight 控件,更不用说复杂控件的控件模板了。

如果有人能找到一个好的解决方案,我会改变我的答案,但目前似乎没有一个......

编辑:

这是一个不错的小解决方法,似乎可以完成这项工作。只需在定义 silverlight 对象的 html 中添加以下参数:

<param name="windowless" value="true" />

在“无窗口”模式下运行的副作用是自动化不起作用:)

【讨论】:

我认为对于浏览器外的应用程序没有这样的解决方法? 顺便说一句,我非常感谢您对此进行了研究。显然没有人在乎。

以上是关于Silverlight + MVVM + 绑定 = 内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章

Silverlight实用窍门系列:47.Silverlight中元素到元素的绑定,以及ObservableCollection和List的使用区别

不使用数据网格的 Silverlight MVVM 示例?

WPF MVVM之INotifyPropertyChanged接口的几种实现方式(转)

silverlight wcf mvvm

当前的 MVVM 视图模型是不是违反了单一职责原则?

您知道任何使用 MVVM 的真实、有用的 WPF/Silverlight 应用程序吗?