如何解决C#定时器保活机制引起的内存泄露问题
创始人
2024-12-25 05:42:55
0次

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 的引用,而是直接引用回调委托。

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...