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 的引用,而是直接引用回调委托。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: php表单提交实例讲解