C# 定时器导致的内存泄露问题
C# 中有三种定时器,System.Windows.Forms
中的定时器和 System.Timers.Timer
的工作方式是完全一样的,所以,这里我们仅讨论 System.Timers.Timer
和 System.Threading.Timer
1、定时器保活
先来看一个例子:
class Program
{
static void Main(string[] args)
{
Start();
GC.Collect();
Read();
}
static void Start()
{
Foo f = new Foo();
System.Threading.Thread.Sleep(5_000);
}
}
public class Foo
{
System.Timers.Timer _timer;
public Foo()
{
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += timer_Elapsed;
_timer.Start();
}
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
WriteLine("System.Timers.Timer Elapsed.");
}
~Foo()
{
WriteLine("---------- End ----------");
}
}
运行结果如下:
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
...
在 Start
方法结束后,Foo
实例已经失去了作用域,按理说应该被回收,但实际并没有(因为析构函数没有执行,所以肯定实例未被回收)。
这就是定时器的 保活机制,因为定时器需要执行 timer_Elapsed
方法,而该方法属于 Foo
实例,所以 Foo
实例被保活了。
但多数时候这并不是我们想要的结果,这种结果导致的结果就是 内存泄露,解决方案是:先将定时器 Dispose
。
public class Foo : IDisposable
{
...
public void Dispose()
{
_timer.Dispose();
}
}
一个很好的准则是:如果类中的任何字段所赋的对象实现了IDisposable
接口,那么该类也应当实现 IDisposable
接口。
在这个例子中,不止
Dispose
方法,Stop
方法和设置AutoReset = false
,都能起到释放对象的目的。但是如果在Stop
方法之后又调用了Start
方法,那么对象依然会被保活,即便Stop
之后进行强制垃圾回收,也无法回收对象。
System.Timers.Timer
和 System.Threading.Timer
的保活机制是类似的。
保活机制是由于定时器引用了实例中的方法,那么,如果定时器不引用实例中的方法呢?
2、不保活下 System.Timers.Timer
和 System.Threading.Timer
的差异
要消除定时器对实例方法的引用也很简单,将 timer_Elapsed
方法改成 静态 的就好了。(静态方法属于类而非实例。)
改成静态方法后再次运行示例,结果如下:
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
---------- End ----------
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
System.Timers.Timer Elapsed.
...
Foo
实例是被销毁了(析构函数已运行,打印出了 End),但定时器还在执行,这是为什么呢?
这是因为,.NET Framework 会确保 System.Timers.Timer
的存活,即便其所属实例已经被销毁回收。
如果改成 System.Threading.Timer
,又会如何?
class Program
{
static void Main(string[] args)
{
Start();
GC.Collect();
Read();
}
static void Start()
{
Foo2 f2 = new Foo2();
System.Threading.Thread.Sleep(5_000);
}
}
public class Foo2
{
System.Threading.Timer _timer;
public Foo2()
{
_timer = new System.Threading.Timer(timerTick, null, 0, 1000);
}
static void timerTick(object state)
{
WriteLine("System.Threading.Timer Elapsed.");
}
~Foo2()
{
WriteLine("---------- End ----------");
}
}
注意,这里的 timerTick
方法是静态的。运行结果如下:
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
System.Threading.Timer Elapsed.
---------- End ----------
可见,随着 Foo2
实例销毁,_timer
也自动停止并销毁了。
这是因为,.NET Framework 不会保存激活 System.Threading.Timer
的引用,而是直接引用回调委托。
C# 定时器导致的内存泄露问题的更多相关文章
- dotnet 6 在 Win7 系统证书链错误导致 HttpWebRequest 内存泄露
本文记录我将应用迁移到 dotnet 6 之后,在 Win7 系统上,因为使用 HttpWebRequest 访问一个本地服务,此本地服务开启 https 且证书链在此 Win7 系统上错误,导致应用 ...
- logging 模块误用导致的内存泄露
首先介绍下怎么发现的吧, 线上的项目日志是通过 logging 模块打到 syslog 里, 跑了一段时间后发现 syslog 的 UDP 连接超过了 8W, 没错是 8 W. 主要是 logging ...
- 深度:ARC会导致的内存泄露
iOS提供了ARC功能,很大程度上简化了内存管理的代码. 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露. 下面列举两种内存泄露的情况. 1,循环参照 A有个属性参照B,B有个属性 ...
- 可能会导致.NET内存泄露的8种行为
原文连接:https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/作者 Michael Shpilt.授权翻译,转载请保 ...
- JavaScript之详述闭包导致的内存泄露
一.内存泄露 1. 定义:一块被分配的内存既不能使用,也不能回收.从而影响性能,甚至导致程序崩溃. 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占 ...
- 避免使用CreateThread函数,导致的内存泄露
原文链接:http://blog.csdn.net/solosure/article/details/6262877
- Android中Handler导致的内存泄露
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html Consider the follo ...
- Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法
内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是 ...
- 在iOS上自动检测内存泄露
手机设备的内存是一个共享资源.应用程序可能会不当的耗尽内存.崩溃,或者遭遇大幅度的性能降低. Facebook iOS客户端有很多功能,并且它们共享同一块内存空间.如果任何特定的功能消耗过多的内存,就 ...
随机推荐
- [译文] 为什么你在 C# 里总是应该使用 "var" 关键字
[译文] Why You Should Always Use the 'var' Keyword in C# (为什么你总是应该在 C# 里使用 "var" 关键字) Using ...
- java项目中rmi远程调用实例
1.创建一个学生实体类Student.java: package com.RMIdemo.entity; @SuppressWarnings("serial") public cl ...
- Docker Swarm Mode简介与核心概念
什么是Docker Swarm Docker Swarm是Docker官方的一种容器编排方案,用于管理跨主机的Docker容器,可以快速对指定服务进行水平扩展.部署.删除 一个Docker Swarm ...
- 如何在Linux上创建,列出和删除Docker容器
本篇文章介绍的内容是关于在Linux机器上创建,列出和删除docker容器,下面我们来看具体的内容. 1.启动Docker容器 使用下面的命令启动新的Docker容器.这将启动一个新的容器,并为你提供 ...
- 洛谷$P4318$ 完全平方数 容斥+二分
正解:容斥/杜教筛+二分 解题报告: 传送门$QwQ$ 首先一看这数据范围显然是考虑二分这个数然后$check$就计算小于等于它的不是讨厌数的个数嘛. 于是考虑怎么算讨厌数的个数? 看到这个讨厌数说, ...
- ECShop二次开发指南-文件结构(二)
ecshop文件架构说明 注意:因各版权不一,大概参考/* ECShop 2.5.1 的结构图及各文件相应功能介绍 ECShop2.5.1_Beta upload 的目录 ┣ activity.p ...
- 机器学习之路--Matplotlib
1.绘制折线图 在pandas里面有一种数据类型为datatime ,可以将不规范的日期改为:xxxx-xx-xx import pandas as pd import numpy as np a = ...
- 组件的props属性和state状态
props属性: 我使用代码来说明React中props属性: // Profile.jsx import React from 'react' ; export default Class Prof ...
- 大白话带你梳理一下Dubbo的那些事儿
首先声明,本文并不是什么代码实战类型的文章,适合于想对dubbo有更加全面认识的读者阅读,文章不会过于深奥,只是将一系列的知识点串通起来,帮助读者温故而知新. RPC服务的介绍 相信有过一些分布式开发 ...
- SSM三大框架整合思路
1.Dao层: Mybatis的配置文件:SqlMapConfig.xml 不需要配置任何内容,需要有文件头.文件必须存在. applicationContext-dao.xml: mybatis整合 ...