【问题标题】:Understanding memory leaks了解内存泄漏
【发布时间】:2019-06-02 00:41:46
【问题描述】:

我正在尝试了解 WPF 内存泄漏,在阅读了该主题后,我有一些不清楚的地方。

问题最好来源于例子,所以我们来定义:

型号:

public class Mom : INotifyPropertyChanged
{
   public ObservableCollection<Kid> Kids { get; set; }

   private string name;
   public string Name
   {
       get => name;
       set => Set(ref name, value);
   }

   public event PropertyChangedEventHandler PropertyChanged;

   protected void Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
   {
       field = newValue;
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }
}

ViewModel (DataContext) 可能如下所示:

public class MomViewModel, INotifyPropertyChanged
{
   private Mom selected;
   public Mom Selected
   {
       get => selected;
       set => Set(ref selected, value);
   }
}

现在我想问一下关于 XAML 中这两种绑定方案的问题:

第一次绑定:

<ListView ItemsSource="{Binding Selected.Kids}">
...
</ListView >

第二次绑定:

<TextBlock Text="{Binding Selected.Kids.Count}" />

现在想象一下,在 ViewModel 中,我们有一个计时器,它每秒分配一个新的妈妈。那是Selected = new Mom { .. };

第一季度: 绑定 1 会产生内存泄漏吗?属性是 ObservableCollection 类型,它实现了 INotifyPropertyChanged,但属性本身没有(只是常规的 get、set)。

第二季度: 绑定 2 会产生内存泄漏吗?绑定直接针对来自 Collection 且未实现 INotifyPropertyChanged 的​​ Count

请注意,视图 (XML) 本身永远不会被破坏 - 只有“Selected”属性每秒更改一次。我(也)不清楚 WPF 何时允许垃圾收集 - 仅当视图被销毁或绑定更改时。我的测试在这里没有定论...

谢谢。

【问题讨论】:

  • 你测试的时候发生了什么?
  • GC 确实启动了,但内存没有释放。
  • @mjwills 查看 VS 2017 中的标准分析器。上面的示例是我测试的一个非常简单的设置。所以我得出的结论是,在我尝试缩小 VS 中的内存使用量(我没有太多经验)之前,我需要了解上述 2 个绑定应该发生什么,

标签: c# wpf data-binding memory-leaks


【解决方案1】:

在以下示例代码中,在将 Selected 源属性设置为新的 Mom 对象后,Mom 的先前实例将符合垃圾回收条件,无论您绑定到 Selected.Kids 还是 @ 987654325@:

public sealed class MomViewModel : INotifyPropertyChanged, IDisposable
{
    private readonly System.Timers.Timer _timer = new System.Timers.Timer();

    public MomViewModel()
    {
        _timer.Interval = 2000;
        _timer.Elapsed += (s, e) => Selected = new Mom();
        _timer.Start();
    }

    private Mom selected;
    public Mom Selected
    {
        get => selected;
        set => Set(ref selected, value);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
    {
        field = newValue;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void Dispose()
    {
        _timer.Dispose();
    }
}

您不会通过在视图中设置以下绑定来引入任何内存泄漏:

<ListView ItemsSource="{Binding Selected.Kids}" />
<TextBlock Text="{Binding Selected.Kids.Count}" />

【讨论】:

    【解决方案2】:

    您拥有的任何 WPF 特定部分都不会导致内存泄漏。

    WPF 绑定是弱引用,因此它们本身并不能使事物保持活力。 如果绑定到未实现 inotifypropertychanged 的​​ poco,则可能会发生内存泄漏。你避免了。 无论您是否提出在 setter 中更改的属性都无关紧要。因此 count 也不会导致任何内存泄漏。

    如果您在任何地方遇到问题,看起来更有可能是您如何保留对您每秒钟都在更新的这些妈妈的引用。有些东西仍然对这些有参考,并且不允许它超出范围。解决方法可能很简单,只需将年长的妈妈从 observablecollection 中取出并丢弃即可。

    如果您想了解在复杂的企业级应用程序中阻止垃圾收集的确切原因,那么您可以尝试使用 redgate Ants profiler。有免费试用。

    【讨论】: