C# 中有三种定时器,System.Windows.Forms 中的定时器和 System.Timers.Timer 的工作方式是完全一样的,所以,这里我们仅讨论 System.Timers.TimerSystem.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.TimerSystem.Threading.Timer 的保活机制是类似的。

保活机制是由于定时器引用了实例中的方法,那么,如果定时器不引用实例中的方法呢?

2、不保活下 System.Timers.TimerSystem.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# 定时器导致的内存泄露问题的更多相关文章

  1. dotnet 6 在 Win7 系统证书链错误导致 HttpWebRequest 内存泄露

    本文记录我将应用迁移到 dotnet 6 之后,在 Win7 系统上,因为使用 HttpWebRequest 访问一个本地服务,此本地服务开启 https 且证书链在此 Win7 系统上错误,导致应用 ...

  2. logging 模块误用导致的内存泄露

    首先介绍下怎么发现的吧, 线上的项目日志是通过 logging 模块打到 syslog 里, 跑了一段时间后发现 syslog 的 UDP 连接超过了 8W, 没错是 8 W. 主要是 logging ...

  3. 深度:ARC会导致的内存泄露

    iOS提供了ARC功能,很大程度上简化了内存管理的代码. 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露. 下面列举两种内存泄露的情况. 1,循环参照 A有个属性参照B,B有个属性 ...

  4. 可能会导致.NET内存泄露的8种行为

    原文连接:https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/作者 Michael Shpilt.授权翻译,转载请保 ...

  5. JavaScript之详述闭包导致的内存泄露

    一.内存泄露 1. 定义:一块被分配的内存既不能使用,也不能回收.从而影响性能,甚至导致程序崩溃. 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占 ...

  6. 避免使用CreateThread函数,导致的内存泄露

    原文链接:http://blog.csdn.net/solosure/article/details/6262877

  7. Android中Handler导致的内存泄露

    http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html Consider the follo ...

  8. Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法

    内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是 ...

  9. 在iOS上自动检测内存泄露

    手机设备的内存是一个共享资源.应用程序可能会不当的耗尽内存.崩溃,或者遭遇大幅度的性能降低. Facebook iOS客户端有很多功能,并且它们共享同一块内存空间.如果任何特定的功能消耗过多的内存,就 ...

随机推荐

  1. JVM 体系结构与工作方式

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  2. [梁山好汉说IT] 以水浒传为例讲解贝叶斯定理

    0x00 摘要 看看呼延灼如何利用贝叶斯定理来判断 "自己是否是公明哥哥的心腹". 0x01 IT概念 1. 贝叶斯定理 贝叶斯定理是用来解决"逆概率"问题的, ...

  3. AndroidStudio插件大全

    Android-Studio-Plugins 原文地址:https://github.com/itgoyo/Android-Studio-Plugins 欢迎star~~~ Android-Studi ...

  4. mac系统Docker安装Redis教程

    在之前介绍过mac系统安装docker,以及docker安装mysql的傻瓜式教程,如果有需要参考的朋友,可以看下面文章: MacOS安装Docker傻瓜式教程 mac系统下docker安装配置mys ...

  5. [Oracle]Oracle的闪回归档

    Oracle的闪回归档 场景需求,由于管理数据库的一些核心表,在实施初期会有人为误删除的问题.Oracle 11gR2提供了闪回归档的特性,可以保证不用RMAN来恢复误删除的数据.实践如下: 1.创建 ...

  6. 「2015南阳CCPC D」金砖 解题报告

    金砖 Problem 有一个长度为L的板凳,可以放一排金砖,金砖不能重叠.特别的,摆放的金砖可以超出板凳,前提是必须保证该金砖不会掉下去,即该金砖的重心必须在板凳上. 每块金砖都一个长度和价值,且金砖 ...

  7. .Net Core 认证系统之基于Identity Server4 Token的JwtToken认证源码解析

    介绍JwtToken认证之前,必须要掌握.Net Core认证系统的核心原理,如果你还不了解,请参考.Net Core 认证组件源码解析,且必须对jwt有基本的了解,如果不知道,请百度.最重要的是你还 ...

  8. html1,初识html

    vs code编辑器 安装插件 chinese 中文支持 open in browser 快速预览文件 view in browser 快捷键 快捷键 描述 shift + end 选中从光标到行尾 ...

  9. 【转】Vim显示中文乱码

    Windows下,在Vim中如果想让中文正常显示,可以在 Vim安装目录下找到_vimrc 文件,用记事本打开就行,然后在其中加入如下语句:   set fileencodings=gb2312,gb ...

  10. input 只允许输入小数

    oninput = "value=value.replace(/[^\d]/g,'')" 输入浮点数不好使 突发奇想自己写一个与众不同的... oninput="valu ...