dotnet 6 使用 DependentHandle 关联对象生命周期

打印 上一主题 下一主题

主题 898|帖子 898|积分 2698

本文将告诉大家在 dotnet 6 新加入的 System.Runtime.DependentHandle 的类型的使用方法,通过 DependentHandle 可以实现将某个对象的引用生命周期和另一个对象关联起来
如 DependentHandle 结构体的构造函数,要传入两个对象作为参数,这两个对象参数分别是 target 和 dependent 参数对象,表示的意义是将这两个对象通过 DependentHandle 结构体建立关联。让 target 对象关联上 dependent 对象的生命周期,在 dependent 对象没有被释放之前,不会先释放 target 对象。功能上和 ConditionalWeakTable 差不多,只是 DependentHandle 作为 ConditionalWeakTable 的底层实现支持,直接使用 DependentHandle 可以有更多的控制
原本咱可以使用 ConditionalWeakTable 将对象进行关联,实现到将某个对象关联到另一个对象的生命周期上,只要另一个对象没有被释放,那么关联的对象也不会被释放。如此可以在不改动原有代码的前提下,让两个毫不关联的对象进行关联。例如可以用来实现缓存模块的功能
然而 ConditionalWeakTable 算是一个上层的封装,如果想要做更多的定制功能,那就可以采用在 dotnet 6 新加入的 System.Runtime.DependentHandle 结构体。这个结构体提供的是一对一的关联关系,而且可以方便调用 Dispose 方法解除关联性
接下来将写一个例子来告诉大家 DependentHandle 类型的使用方法
新建一个 WPF 项目,在界面放三个按钮,分别是进入断点按钮,用于进入断点看对象是否被释放。释放引用按钮,将关联的对象释放引用。由于在 .NET 里面,释放引用不代表立刻被回收,再加上一个 GC 按钮,用来点击的时候触发 GC 逻辑
  1.         <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
  2.             <StackPanel.Resources>
  3.                
  4.             </StackPanel.Resources>
  5.             <Button Click="Button_Click">
  6.                 进入断点
  7.             </Button>
  8.             <Button x:Name="FreeObjectButton" Click="FreeObjectButton_Click">
  9.                 释放引用
  10.             </Button>
  11.             <Button x:Name="GCButton" Click="GCButton_Click">
  12.                 GC
  13.             </Button>
  14.         </StackPanel>
复制代码
在后台代码里面定义 Foo1 和 Foo2 两个类型,定义类型的用途是在于方便在内存里面找到这两个类型的对象
  1. public class Foo1
  2. {
  3. }
  4. public class Foo2
  5. {
  6. }
复制代码
这两个类型没有相互关联性,在 MainWindow 添加一个 Foo1 类型的字段,如此即可让 Foo1 类型的对象被 MainWindow 引用,不会被释放
  1.     public MainWindow()
  2.     {
  3.         InitializeComponent();
  4.         Loaded += MainWindow_Loaded;
  5.     }
  6.     private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  7.     {
  8.         var foo1 = new Foo1();
  9.         _foo1 = foo1;
  10.     }
  11.     private Foo1? _foo1;
复制代码
如果在 MainWindow_Loaded 方法里面,创建一个 Foo2 对象,而让这个对象没有地方引用,那自然很快就会 Foo2 对象就会被释放
  1.     private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  2.     {
  3.         var foo1 = new Foo1();
  4.         new Foo2();
  5.         _foo1 = foo1;
  6.     }
复制代码
如果将 Foo2 通过 DependentHandle 实现和 Foo1 关联,加入到关联列表里面,如以下逻辑,将可以让 Foo2 不会被释放
  1.     private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  2.     {
  3.         var foo1 = new Foo1();
  4.         HandleList.Add(new DependentHandle(foo1, new Foo2()));
  5.         _foo1 = foo1;
  6.     }
  7.     private List<DependentHandle> HandleList { get; } = new List<DependentHandle>();
复制代码
假如 DependentHandle 是另外的某些类型,对 Foo2 的对象也有强引用关系,那自然 Foo2 不会被释放,只是不会被释放的理由是被 HandleList 给引用。但本文告诉大家的 DependentHandle 是不会对 Foo2 有强引用的,只是对 Foo2 关联 Foo1 对象
实现释放引用按钮的功能,点击按钮,将 Foo1 的对象引用释放,如此即可让 Foo1 的对象可以被回收
  1.     private void FreeObjectButton_Click(object sender, RoutedEventArgs e)
  2.     {
  3.         _foo1 = null;
  4.     }
复制代码
由于在 .NET 里面,不会立刻回收,实现 GC 按钮的功能,手动触发回收
  1.     private void GCButton_Click(object sender, RoutedEventArgs e)
  2.     {
  3.         GC.Collect();
  4.         GC.WaitForFullGCComplete();
  5.     }
复制代码
实现断点按钮的逻辑
  1.     private void Button_Click(object sender, RoutedEventArgs e)
  2.     {
  3.         Debugger.Break();
  4.     }
复制代码
运行代码,先点击一下释放按钮,让 _foo 释放引用,接着点击 GC 按钮触发回收逻辑。多次点击 GC 按钮,等待一下,然后点击进入断电按钮,自己打上断点,通过 VS 的内存调试,可以看到 Foo1 和 Foo2 对象都没有存在内存里面
同时在 HandleList 列表里面,可以看到有一个 DependentHandle 对象,但是此对象里面的 Target 和 Dependent 都是 null 空对象
这就是证明了,通过 DependentHandle 可以建立从 Target 到 Dependent 的引用关联,在 Dependent 被回收之前,不会回收 Target 对象。在 Dependent 被回收之后,才会回收 Target 对象
在经过测试,使用 DependentHandle 的回收速度是比较慢的,很多时候,不是第一次点击 GC 按钮进行回收就能回收掉 Foo1 和 Foo2 对象的,而是需要多次点击
使用 dotnet 6 加入的 DependentHandle 进行底层的控制,更好写出符合自己业务逻辑的对象关联逻辑。例如实现自己的缓存库等。这个 Dependent 的功能是需要 CLR 层面提供的,也就是说这个类型只能在 dotnet 6 和更高版本使用,详细请看 dotnet ConditionalWeakTable 的底层原理
更多请看 DependentHandle Struct (System.Runtime) Microsoft Docs
以上的代码放在githubgitee 欢迎访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 8378751cc0766bc804fa8a1ae5ef3e0766dd5b99
复制代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 8378751cc0766bc804fa8a1ae5ef3e0766dd5b99
复制代码
获取代码之后,进入 LemjallbabelyeeburKemkubejer 文件夹

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表