C# 委托回调引发的编程反思

前言

之前的过开发程中,我愈发觉得面对复杂的界面要求,最好还是用UserControl将不同模块的界面设计单独封装,以应对客户频繁地需求更改。

这样做能够在面对对不同的UI要求时,动态的加载预先设计好的特定模块的UserControl,不需要用代码对界面进行复杂的控制,否则要用代码控制一个个控件的生成与显示。设计之初费力,后面维护起来比较方便。

介绍

最近开发新工具,针对不同的模块的数据展示我设计了不同的布局单独封装为UserControl,放置在PanelControl中作为数据展示。

为了能够灵活的进行数据初始化,我给每个UserControl都订阅了主程序的通知事件。

使用委托回调的方式,主程序中调用委托,子控件控件中自动执行具体的初始化方法。

//定义一个带有一个参数的无返回值的委托
delegate void DelegateUpdateUserControl(DeviceConfig item);
//声明委托对象
private DelegateUpdateUserControl UpdateUserControl;


大事件发生

在测试过程中,数据的动态加载以及UserControl的切换展示没有问题。但随着切换次数的增加,界面变得越来越卡顿。

此时的我心中顿时警铃大作——有些该被释放掉的资源没有被释放掉?!但是每次进行界面切换,我都会调用Clear方法,将PanelControl里面的对象清空(PanelControl.Clear();),这时我开始怀疑UserControl对象仍在被主程序引用,系统无法进行GC.Collect(),对象没有被回收。

测试

我在UserControl中的订阅方法里增加一行Console.WriteLine方法,进行测试。

理想情况下每切换一次UserControl,会调用一次委托,让UserControl绑定的委托方法输出一条信息。测试却发现,第一次会输出1条、第二次输出2条、第三次输出3条…

这说明了除了第一次调用委托方法,后面每次调用都有不止一个对象在响应且数量依次增加,看来确实是每次的UserControl都没有被成功回收。

结合.NET的垃圾回收机制,断定UserControl对象还在被主程序引用,导致没有被成功释放,可鞥是没有解除委托与方法的绑定造成的。

我将针对这个问题做了以下优化:

修改以下代码:

//在切换控件时,先清空原先的旧UserControl
//清空PanelControl所有控件,通常情况下只存在一个控件
PanelControlContainer.Controls.Clear();


改为:

if (PanelControlContainer.Controls.Count > 0)
{
//获取到UserControl(PanelControlContainer)对象
UControlProperty control =(UControlProperty)PanelControlContainer.Controls[0];
//取消事件挂载,解除委托绑定
UpdateUserControl -= control.UpdateControls;
//从PanelControl中移除旧UserControl
PanelControlContainer.Controls.Remove(control);
//主动释放资源
control.Dispose();
}


以上步骤主要是取消事件挂载,解除主程序与UserControl对象之间的引用关系,及时让GC回收资源。

结果

经最后测试,调用委托后,只会有当前最新的UserControl对象响应,界面切换时卡顿的现象消失。

扩展——.NET垃圾回收机制

遇到这种情况,我们必须对.NET的垃圾回收机制有一定了解,借此机会聊聊GC回收机制。

以下内容参考《CLR via C#》(第四版)(CLR:Common Language Runtime,公共语言运行时)

在C#中,当我们创建一个对象时,它的引用会被存储在栈(Stack)中,而对象的实际数据会被存储在堆(Heap)中。这是.NET运行时自动进行的内存管理,称为垃圾回收(Garbage Collection, GC)。

每个对象都有两个开销字段:类型对象指针和同步块索引,在64位应用程序中各占8个字节。

所有引用类型的变量都称为根对象,这些根对象是垃圾回收的起点,括局部变量、全局变量、静态字段等‌;

CLR开始回收时会暂停所有线程,,防止线程在CLR检查期间访问对象并更改其状态;

然后CLR进入GC的标记阶段,CLR遍历堆中的所有对象,将同步索引块中的一位设为0;

然后CLR检查所有活动根,任何根引用了堆上的对象,CLR都会标记那个对象,将同步索引块中的位设为1;

一个对象被标记后,CLR会检查那个对象的根,标记他们引用的对象,如果对象已经被标记,就不重新检查对象的字段,避免引起死循环;

检查结束后,已标记的对象至少有一个根在引用,我们说这种对象是可达的,不能被垃圾回收;

GC删除未被标记的对象,把被标记的对象挪到一块连续的空间,进行压缩,避免空间碎片化的问题;

作为压缩的一个步骤,CLR还要从每个根减去所引用对象在内存中的偏移字节数,保证每个根引用的是之前的对象;

最后恢复执行之前被暂停的线程;

总结

给对象事件绑定委托,释放对象前,要主动解除委托绑定,避免出现资源无法释放的问题出现;

CLR是基于代的垃圾回收机制,自动工作并不定时释放不会再被访问的资源。

收藏
点赞
分享
在看

原创文章,作者:速盾高防cdn,如若转载,请注明出处:https://www.sudun.com/ask/205791.html

Like (0)
速盾高防cdn的头像速盾高防cdn
Previous 6天前
Next 3天前

相关推荐

  • dnf为什么总是卡顿?

    DNF作为一款备受欢迎的游戏,吸引了无数玩家的关注。然而,许多玩家在游戏过程中却遇到了令人沮丧的问题——卡顿。这个问题似乎困扰着许多玩家,那么究竟是什么原因导致了DNF总是卡顿?今…

    问答 2024年3月24日
    0
  • 被cc穿透攻击如何防护?

    CC(Challenge Collapsar)攻击是一种常见的 DDoS 攻击类型,它通过持续发送大量的正常请求来消耗目标服务器的资源,使其无法正常响应合法用户的请求。以下是防范 …

    问答 2024年2月8日
    0
  • 如何使用磨皮滤镜打造完美肌肤?

    如何使用磨皮滤镜打造完美肌肤?这是许多人都想要知道的秘密。随着网络行业的发展,人们对于自己的外表要求也越来越高。而磨皮滤镜作为一种常用的美颜工具,已经成为了许多人打造完美肌肤的必备…

    问答 2024年4月5日
    0
  • gtalkservice是什么?(详细介绍)

    在当今网络行业中,有许多通信工具被广泛使用,而其中一个备受关注的工具就是gtalkservice。那么,gtalkservice究竟是什么?它有哪些功能和特点?如何使用它?与其他通…

    问答 2024年4月11日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注