C# 中有三種定時器,System.Windows.Forms
中的定時器和 System.Timers.Timer
的工做方式是徹底同樣的,因此,這裏咱們僅討論 System.Timers.Timer
和 System.Threading.Timer
函數
先來看一個例子:code
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 ----------"); } }
運行結果以下:orm
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
以後進行強制垃圾回收,也沒法回收對象。string
System.Timers.Timer
和 System.Threading.Timer
的保活機制是相似的。it
保活機制是因爲定時器引用了實例中的方法,那麼,若是定時器不引用實例中的方法呢?class
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
的引用,而是直接引用回調委託。