内存泄漏之DispatcherTimer

news2024/10/7 16:22:47

https://www.jianshu.com/p/7e9ecb383bd0

我们经常会在程序中使用DispatcherTimer,但是如果一不小心就会发生内存泄漏,请看下面的Demo:

内存泄漏代码

我创建了一个简单的窗口Example1.xaml:

<Window x:Class="MemoryLeak.Example.Example1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Window>

Example1.xaml.cs中的代码如下:

public partial class Example1 : Window
{
    //这里产生一个大的内存占用,约50MB,用于在任务管理器看到这个窗口Show出来以后,进程内存占用剧增的现象
    private readonly List<string> _bigList = ExampleHelper.BigList();
 
    public Example1()
    {
        InitializeComponent();

        DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
        timer.Tick += Timer_Tick;
        timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine(DateTime.Now);
    }
}

内存泄漏现象

Example1 window = new Example1();
window.Show();

然后将此window直接关闭,那么显然这个window生命周期结束,再无引用。执行:

GC.Collect();

window占用的50MB内存应该被回收,然后在任务管理器中看此进程,其内存并没有被回收。什么东西阻止了我回收?阻止回收的根本原因是仍然有人引用!我们来挖一下,深层次的原因在哪里。

源码分析

timer生命周期分析

上面的代码非常的简单,就是在窗口构造函数执行的时候 new 了一个 DispatcherTimer,“老师”告诉我们,一个变量的生命周期在大括号内,也就是构造函数执行完毕之后timer就应该被回收了,但是事实上,窗口关闭以后,仍然每秒输出当前时间。到底发生了什么事呢?
我们去看看 DispatcherTimer 的源码发生了什么事:

public class DispatcherTimer
{
    private Dispatcher _dispatcher;
    public void Start()
    {
        //省略一些跟我们主题无关的源代码
        this.Restart();
    }
    private void Restart()
    {
        //省略一些跟我们主题无关的源代码
        this._dispatcher.AddTimer(this);
    }
    public void Stop()
    {
        //省略一些跟我们主题无关的源代码
        this._dispatcher.RemoveTimer(this);
    }
}

public class Dispatcher
{
    public static Dispatcher CurrentDispatcher
    {
        get { return Dispatcher.FromThread(Thread.CurrentThread) ?? new Dispatcher(); }
    }

    private List<DispatcherTimer> _timers = new List<DispatcherTimer>();
    internal void AddTimer(DispatcherTimer timer)
    {
        //省略一些跟我们主题无关的源代码
        this._timers.Add(timer);
    }
}

从源码中我们可以看到,DispatcherTimer_dispatcher对象里面有一个_timers对象,timer.Start()调用的时候将timer实例加入了_timers列表中,造成了对timer对象的强引用。那问题来了,_dispatcher对象是哪里来的?DispatcherTimer的源码显示它看起来是一个实例变量,应该不会造成强引用呀。呵呵。

这个_dispatcher就是Dispatcher.CurrentDispatcherDispatcher. CurrentDispatcher是一个static的变量哦,什么是staticstatic对象就是生命周期跟进程的生命周期同样长的对象,所以如果你不调用timer.Stop()timer_timers中移除,你再也无法回收timer了,特别是window又关闭了,这个timer除了内部被引用外,外部再也没有办法通过正常的途径去找到它了,于是timer的内存泄漏产生。

window生命周期分析

虽然DispatcherTimer无法回收,但是我们本文的主题是想回收Example1这个window对象,真是坑啊,这么简单的代码有2个内存泄漏产生了。

言归正传,window.Show()完以后,点击window的关闭按钮,关闭了窗口。GC.Collect()并不能回收这个window占用的内存,那么一定有人引用了这个window,问题出现在哪里呢?
让我们仔细看看这句代码:timer.Tick += Timer_Tick;,这句代码的完整形式应该是timer.Tick += this.Timer_Tick;我们知道事件的工作原理是观察者模式,要实现观察者模式就必须有目标Target和处理函数Method,那么timer.Tick += Timer_Tick;这句话将this作为观察者模式中的Target,Timer_Tick作为Method,当事件发生的时候可以通过Target调用Target中的Method,所以timer.Tick += Timer_Tick;这句话就将window强引用了。

内存泄露原因深度剖析及解决措施

上述问题中谈到timer.Tick += Timer_Tick;,这句代码造成了对window的强引用,我们知道还有很多方式可以添加事件处理函数,比如匿名方法:

public Example1()
{
    InitializeComponent();

    DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
    timer.Tick += (s, e) => { Console.WriteLine(DateTime.Now); };
    timer.Start();
}

这个时候会不会造成window无法回收呢?答案是:window可以回收。
但如果是这样呢:timer.Tick += (s, e) => { Console.WriteLine(this.Width); };答案是:window无法回收。
timerStop()无法回收timer这一点我们已经很清楚了,但是匿名函数为什么有时会阻止window回收有时又不会阻止?
请看我的另一篇文章:《原来是这样:C#中的匿名函数 & 闭包(未完成)》

至此DispatcherTimer造成的内存泄漏分析完毕,我们知晓了微软是如何维护计时器的,也知道造成泄漏的根本原因就是还有引用,解决措施就很简单了:在不需要用到计时器的时候Stop就可以了,在Tick方法中停止也是可以的。



作者:古意昌
链接:https://www.jianshu.com/p/7e9ecb383bd0#
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/554149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Keep your Eyes on the Lane Real-time Attention-guided Lane Detection 论文精读

关注车道&#xff1a;实时注意力引导车道线检测 摘要 现代车道线检测方法在复杂的现实世界场景中取得了显著的性能&#xff0c;但许多方法在保持实时效率方面存在问题&#xff0c;这对自动驾驶汽车很重要。在这项工作中&#xff0c;我们提出了LaneATT&#xff1a;一种基于锚点…

【降维打击】T分布随机近邻嵌入(T-SNE)Python实践

近几天看到论文里面有T分布随机近邻嵌入&#xff08;T-distributed stochastic neighbor embedding, T-SNE&#xff09;这种可视化方法&#xff0c;以前好像也看到过&#xff0c;但没有系统了解过&#xff0c;现有时间正好实践记录一下。 1. T-SNE简介 T-SNE是一种降维方法&am…

搭建监控日志系统

在微服务或者集群架构中&#xff0c;一次请求的调用会跨多个服务&#xff08;web&#xff0c;mysql&#xff0c;feign等&#xff09;、多个模块&#xff08;用户模块&#xff0c;商品模块等&#xff09;、多个容器&#xff08;用户模块可能有多个实例&#xff09;&#xff0c;这…

【科普】干货!带你从0了解移动机器人(二)—— 移动机器人硬件组成

移动机器人是一个多功能于一体的综合系统&#xff0c;内容涵盖了传感器技术、自动化技术、信息处理、电子工程等&#xff0c;它集环境感知、动态决策与规划于一体&#xff0c;是目前科学技术发展最活跃的领域之一。移动机器人的各种组件之间需要协同工作才能实现机器人的自主移…

【源码解析】RuoYi-Vue-Plus翻译功能 Translation 源码分析

类说明功能Translation通用翻译注解标注需要翻译的字段&#xff0c;用于实体类字段上TranslationType翻译类型注解标注翻译字段的实现类型&#xff0c;用于实现类上标注TransConstant翻译常量TranslationType 类型常量TranslationConfig翻译模块配置类配置初始化&#xff0c;设…

深度学习基础入门篇[9.2]:卷积之1*1 卷积(残差网络)、2D/3D卷积、转置卷积数学推导、应用实例

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

一文了解customRef 自定义ref使用

概念 按照文档中的说明&#xff1a;customRef 可以用来创建一个自定义的 ref&#xff0c;并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数&#xff0c;该函数接收 track 和trigger函数作为参数&#xff0c;并且应该返回一个带有 get 和 set 的对象。 其实大致意思…

公有云——阿里云ECS服务器入门精通(IaaS)(2)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 一.ECS 实例规格族介绍 1.实例的架构类型、规格分类&#xff0c;详细信息 2.企业…

网络互联与互联网 - IP 子网划分详解

文章目录 1 概述1.1 划分目的1.2 划分原则1.3 子网掩码 2 IP 子网划分示例3 网工软考真题3.1 判断网络号和主机号3.2 计算可容纳的主机数3.3 子网划分 1 概述 IP 子网划分&#xff1a;实际上就是设计 子网掩码 的过程。原因&#xff1a;由于在五类的IP地址中&#xff0c;网络号…

【SpringBoot】数据校验API

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 数据校验API SpringBoot数据校验数据校验API的…

智能电动「唱主角」,哪些供应商在「领跑」智驾域控制器赛道

新势力&#xff0c;从哪里突围&#xff1f; 造车新势力在过去几年的成绩&#xff0c;已经代表了未来趋势&#xff1a;新能源汽车&#xff0c;尤其是纯电动。而对于智能化软硬件供应商&#xff0c;尤其是新势力供应商来说&#xff0c;亦是如此。 高工智能汽车研究院监测数据显…

深度学习框架-Tensorflow2:特点、架构、应用和未来发展趋势

引言 深度学习是一种新兴的技术&#xff0c;已经在许多领域中得到广泛的应用&#xff0c;如计算机视觉、自然语言处理、语音识别等。在深度学习中&#xff0c;深度学习框架扮演着重要的角色。Tensorflow是一种广泛使用的深度学习框架&#xff0c;已经成为深度学习的事实标准。…

全民拼购为什么能躺赢

大家好&#xff01;我叫小鱼 新商业&#xff0c;新模式 新机会&#xff01; 我们在拼购过程中 往往都觉得商家在亏钱 事实如此吗&#xff1f; 随着全球经济下行&#xff0c;党中央、国务院 高度重视发展流通扩大消费。 为推动流通创新发展&#xff0c;促进商业繁荣&#xff0c;…

回文子串问题

一&#xff1a;最长回文子串&#xff08;leetcode 5&#xff09; 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#x…

盛元广通疾病预防控制中心检测管理信息系统

近些年&#xff0c;在疾病预防控制领域&#xff0c;公共卫生事件的发生都是通过信息化手段在日常工作中加以应用以及广泛深入的探索&#xff0c;加快疾控实验室信息化建设进程&#xff0c;可以有效把控不同类型检测任务中的每个节点&#xff0c;严防不同系统填报多次出现信息误…

SpringBoot——热部署

简单介绍&#xff1a; 在之前我们的项目中&#xff0c;当我们在编写了一个新的类并且要应用的时候&#xff0c;需要手动重启服务器重新部署一下&#xff0c;这个过程需要我们手动去完成&#xff0c;但是很多时候&#xff0c;比如我们在做测试&#xff0c;需要反复的修改代码&a…

第14章_视图

第14章_视图 1. 常见的数据库对象 对象描述表(TABLE)表是存储数据的逻辑单元&#xff0c;以行和列的形式存在&#xff0c;列就是字段&#xff0c;行就是记录数据字典就是系统表&#xff0c;存放数据库相关信息的表。系统表的数据通常由数据库系统维护&#xff0c;程序员通常不…

【Http协议②】http协议格式,请求格式,常见请求方法,请求报文,请求正文

前言: 大家好,我是良辰丫,上一篇文章我们已经了解过了http协议,这篇文章我将带领大家去学习http协议的一些属性,http协议格式,请求格式,常见请求方法,请求报文,请求正文.跟随我的脚步,一起遨游http的海洋.&#x1f49e;&#x1f49e; &#x1f9d1;个人主页&#xff1a;良辰针不…

第14届蓝桥杯省赛真题剖析-2023年5月7日Scratch编程初级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第135讲。 第14届蓝桥杯Scratch省赛真题&#xff0c;这是2023年5月7日举办的省赛&#xff0c;比赛仍然采取线上形式。试…

Spring Boot 的 Starter 以及实现一个自定义Starter

一、了解 Spring Boot Starter Spring Boot Starter 是 Spring Boot 中一个重要概念&#xff0c;它是一种提供依赖项的方式&#xff0c;简化 Spring 应用程序依赖管理&#xff0c;将一组相关的依赖项打包在一起&#xff0c;并提供一个依赖项描述文件&#xff0c;使开发人员可以…