C# 定時器?;顧C制引起的內(nèi)存泄露問題解決
C# 中有三種定時器,System.Windows.Forms 中的定時器和 System.Timers.Timer 的工作方式是完全一樣的,所以,這里我們僅討論 System.Timers.Timer 和 System.Threading.Timer
1、定時器?;?/strong>
先來看一個例子:
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 ----------");
}
}
運行結(jié)果如下:
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 方法結(jié)束后,F(xiàn)oo 實例已經(jīng)失去了作用域,按理說應該被回收,但實際并沒有(因為析構(gòu)函數(shù)沒有執(zhí)行,所以肯定實例未被回收)。
這就是定時器的 保活機制,因為定時器需要執(zhí)行 timer_Elapsed 方法,而該方法屬于 Foo 實例,所以 Foo 實例被?;盍?。
但多數(shù)時候這并不是我們想要的結(jié)果,這種結(jié)果導致的結(jié)果就是 內(nèi)存泄露,解決方案是:先將定時器 Dispose。
public class Foo : IDisposable
{
...
public void Dispose()
{
_timer.Dispose();
}
}
一個很好的準則是:如果類中的任何字段所賦的對象實現(xiàn)了IDisposable 接口,那么該類也應當實現(xiàn) IDisposable 接口。
在這個例子中,不止 Dispose 方法,Stop 方法和設置 AutoReset = false,都能起到釋放對象的目的。但是如果在 Stop 方法之后又調(diào)用了 Start 方法,那么對象依然會被?;?,即便 Stop 之后進行強制垃圾回收,也無法回收對象。
System.Timers.Timer 和 System.Threading.Timer 的?;顧C制是類似的。
?;顧C制是由于定時器引用了實例中的方法,那么,如果定時器不引用實例中的方法呢?
2、不保活下 System.Timers.Timer 和 System.Threading.Timer 的差異
要消除定時器對實例方法的引用也很簡單,將 timer_Elapsed 方法改成 靜態(tài) 的就好了。(靜態(tài)方法屬于類而非實例。)
改成靜態(tài)方法后再次運行示例,結(jié)果如下:
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 實例是被銷毀了(析構(gòu)函數(shù)已運行,打印出了 End),但定時器還在執(zhí)行,這是為什么呢?
這是因為,.NET Framework 會確保 System.Timers.Timer 的存活,即便其所屬實例已經(jīng)被銷毀回收。
如果改成 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 方法是靜態(tài)的。運行結(jié)果如下:
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 的引用,而是直接引用回調(diào)委托。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#中DataTable 轉(zhuǎn)換為 Json的方法匯總(三種方法)
JavaScript Object Notation (Json)是一種輕量級的數(shù)據(jù)交換格式,下面小編給大家介紹三種方法實現(xiàn)DataTable轉(zhuǎn)換成 Json 對象,感興趣的朋友一起看看吧2016-11-11
C#中AutoResetEvent控制線程用法小結(jié)
本文主要來自一道面試題,由于之前對AutoResetEvent的概念比較模糊,面試題題目很簡潔:兩個線程交替打印0~100的奇偶數(shù),你可以先動手試試,我主要是嘗試在一個方法里面完成這個任務,需要的朋友可以參考下2022-07-07

