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 的參考,而是直接參考回呼委托,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/75348.html
標籤:C#
