[Windows翻译]C#和其他GC语言中的Windows Runtime委托和对象生命周期

94 阅读2分钟

原文地址:devblogs.microsoft.com/oldnewthing…

原文作者:devblogs.microsoft.com/oldnewthing…

发布时间:2019年5月22日

在C#和其他GC语言(如JavaScript)中,委托人(最典型的是作为事件处理程序使用)在其闭包中捕获对对象的强引用。这意味着你可以创建超出GC收集能力的引用循环。

using Windows.Devices.Enumeration;

class Circular
{
  DeviceWatcher watcher;

  public Circular()
  {
    watcher = DeviceInformation.CreateWatcher();
    watcher.Added += OnDeviceAdded;
  }

  void OnDeviceAdded(DeviceWatcher sender, DeviceInformation info)
  {
    ...
  }
}

Circular类包含一个对DeviceWatcher的引用,而DeviceWatcher又包含一个回到Circular的引用(通过委托人)。这个循环引用永远不会被收集,因为其中一个参与者是一个DeviceWatcher,这超出了垃圾收集器的知识范围。

从垃圾收集器的角度来看,系统是这样的。

? → delegate → Circular → DeviceWatcher

垃圾收集器完全知道绿色框 "delegate "和 "Circular",因为它们是CLR对象。垃圾收集器不知道虚线框,因为它们是CLR范围之外的外部对象。

垃圾收集器知道的是,有一个来自某个未知外部来源的对delegate的未完成引用,它知道该delegate有一个对Circular对象的引用,它知道Circular对象有一个对某个外部对象的引用,这个外部对象的名字叫DeviceWatcher.但是它不知道DeviceWatcher对象可能有什么引用,因为DeviceWatcher不是一个CLR对象。它不知道DeviceWatcher其实一直都是问号¹

为了避免内存泄漏,你必须打破这种循环引用。理想情况下,有一些自然的地方可以进行这种清理。例如,如果你是一个Page,你可以在你的OnNavigatedFrom方法中进行清理,或者响应Unloaded事件。不太理想的情况下,你可以添加一个清理方法,可能编入IDisposable模式。

有一种特殊情况。XAML框架与CLR有一个秘密协议,XAML可以共享它所拥有的引用的更多详细信息。这些信息使得CLR有可能打破某些类别的循环引用,这些循环引用在XAML代码中是经常出现的。例如,CLR可以在XAML框架提供的信息的帮助下检测到这种循环引用。

<!-- XAML -->
<Page x:Name="AwesomePage" ...>
  ...
  <Button x:Name="SomeNamedButton" ... >
  ...
</Page>

// C#代码背后

partial class AwesomePage : Page
{
  AwesomePage()
  {
    InitializeComponent();
    SomeNamedButton.Click += SomeNamedButton_Click;
  }

  void SomeNamedButton_Click(object sender, RoutedEventArgs e)
  {
    ...
  }
}

这里有一个AwesomePage和SomeNamedButton之间的循环引用,但XAML框架提供的额外信息为CLR提供了足够的信息来识别循环,并在它成为垃圾时收集它。

¹"一直以来都是问号",听起来就像一部M.Night Shyamalan的烂片中的破坏者。


www.deepl.com 翻译