C# 定时器保活机制引起的内存泄露问题解决
C#中有三种定时器,System.Windows.Forms中的定时器和System.Timers.Timer的工作方式是完全一样的,所以,这里我们仅讨论System.Timers.Timer和System.Threading.Timer
1、定时器保活
先来看一个例子:
classProgram { staticvoidMain(string[]args) { Start(); GC.Collect(); Read(); } staticvoidStart() { Foof=newFoo(); System.Threading.Thread.Sleep(5_000); } } publicclassFoo { System.Timers.Timer_timer; publicFoo() { _timer=newSystem.Timers.Timer(1000); _timer.Elapsed+=timer_Elapsed; _timer.Start(); } privatevoidtimer_Elapsed(objectsender,System.Timers.ElapsedEventArgse) { WriteLine("System.Timers.TimerElapsed."); } ~Foo() { WriteLine("----------End----------"); } }
运行结果如下:
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
...
在Start方法结束后,Foo实例已经失去了作用域,按理说应该被回收,但实际并没有(因为析构函数没有执行,所以肯定实例未被回收)。
这就是定时器的保活机制,因为定时器需要执行timer_Elapsed方法,而该方法属于Foo实例,所以Foo实例被保活了。
但多数时候这并不是我们想要的结果,这种结果导致的结果就是内存泄露,解决方案是:先将定时器Dispose。
publicclassFoo:IDisposable { ... publicvoidDispose() { _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.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
----------End----------
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
System.Timers.TimerElapsed.
...
Foo实例是被销毁了(析构函数已运行,打印出了End),但定时器还在执行,这是为什么呢?
这是因为,.NETFramework会确保System.Timers.Timer的存活,即便其所属实例已经被销毁回收。
如果改成System.Threading.Timer,又会如何?
classProgram { staticvoidMain(string[]args) { Start(); GC.Collect(); Read(); } staticvoidStart() { Foo2f2=newFoo2(); System.Threading.Thread.Sleep(5_000); } } publicclassFoo2 { System.Threading.Timer_timer; publicFoo2() { _timer=newSystem.Threading.Timer(timerTick,null,0,1000); } staticvoidtimerTick(objectstate) { WriteLine("System.Threading.TimerElapsed."); } ~Foo2() { WriteLine("----------End----------"); } }
注意,这里的timerTick方法是静态的。运行结果如下:
System.Threading.TimerElapsed.
System.Threading.TimerElapsed.
System.Threading.TimerElapsed.
System.Threading.TimerElapsed.
System.Threading.TimerElapsed.
----------End----------
可见,随着Foo2实例销毁,_timer也自动停止并销毁了。
这是因为,.NETFramework不会保存激活System.Threading.Timer的引用,而是直接引用回调委托。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。