標籤:
通常我們需要定時執行一段任務的時候,我們就需要定時器,這時我們就可以使用c# System.Threading空間中的 Timer定時器;他是個非同步定時器,時間到時每次都是線上程池中分配一個線程去執行任務。下面我們來看一個有趣的例子:
class Program { static void Main(string[] args) { Timer timer = new Timer(TimerCallback,null,0,2000); Console.ReadLine(); } private static void TimerCallback(object o) { Console.WriteLine("in TimerCallback method"); GC.Collect(); } }
當我們在debug模式下運行該段程式時,正如我們期盼的那樣程式會每隔2秒鐘執行該方法,列印出"in TimerCallback method”,而在release模式下執行的時候,只執行一次該方法,字串只列印一次。在這裡我們在調用TimerCallback方法時,強制執行記憶體回收行程,說明在release模式下,記憶體回收行程執行回收演算法時,首先假設所有對象都是可回收的,當將Timer對象賦值給變數t後,t沒有在被引用,因此也就沒有變數引用Timer對象,所以垃圾收集這時會回收Timer對象。那麼為什麼在debug模式下卻能夠運行能,這跟c#編譯器的最佳化方式有關,在release模式下編譯器做了相關的最佳化操作。而在debug模式下,timer對象的產生期是方法的結束,這樣做也是為了調試的方便。要不然在調試時,我們執行到Timer timer = new Timer()後想看timer的值時,已經被記憶體回收行程給回收了,這是我們不期望看到的結果,編譯器如何處理的,我們可以看看編譯器在release模式下和debug模式下對上面的代碼編譯後產生的IL對比我們既知結果。
release模式編譯產生的IL:
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // Code size 32 (0x20) 5 .maxstack 8 6 IL_0000: ldnull 7 IL_0001: ldftn void GCTest.Program::TimerCallback(object) 8 IL_0007: newobj instance void [mscorlib]System.Threading.TimerCallback::.ctor(object, 9 native int)10 IL_000c: ldnull11 IL_000d: ldc.i4.012 IL_000e: ldc.i4 0x7d013 IL_0013: newobj instance void [mscorlib]System.Threading.Timer::.ctor(class [mscorlib]System.Threading.TimerCallback,14 object,15 int32,16 int32)17 IL_0018: pop18 IL_0019: call string [mscorlib]System.Console::ReadLine()19 IL_001e: pop20 IL_001f: ret21 } // end of method Program::Main
debug模式下產生的IL:
1 method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // Code size 33 (0x21) 5 .maxstack 4 6 .locals init ([0] class [mscorlib]System.Threading.Timer timer) 7 IL_0000: nop 8 IL_0001: ldnull 9 IL_0002: ldftn void GCTest.Program::TimerCallback(object)10 IL_0008: newobj instance void [mscorlib]System.Threading.TimerCallback::.ctor(object,11 native int)12 IL_000d: ldnull13 IL_000e: ldc.i4.014 IL_000f: ldc.i4 0x7d015 IL_0014: newobj instance void [mscorlib]System.Threading.Timer::.ctor(class [mscorlib]System.Threading.TimerCallback,16 object,17 int32,18 int32)19 IL_0019: stloc.020 IL_001a: call string [mscorlib]System.Console::ReadLine()21 IL_001f: pop22 IL_0020: ret23 } // end of method Program::Main
從產生的IL中我們可以看出在debug模式下,產生IL比在release模式下多了19行紅色字型的IL指令碼,該指令碼的作用是將15行產生的引用Timer對象的棧上的變數存放到局部變數0中。所以使得在debug模式下該t還被引用,不能夠回收Timer對象,所以也能出現我們期盼的結果,那麼如何在兩種模式下都能得到我們期盼的結果呢。我們可以如下操作。
正確的代碼:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Timer timer = new Timer(TimerCallback,null,0,2000); 6 7 Console.ReadLine(); 8 timer.Dispose(); 9 }10 11 private static void TimerCallback(object o)12 {13 Console.WriteLine("in TimerCallback method");14 15 GC.Collect();16 17 18 }19 }
這時不管是在release模式下還是debug模式下,都會每隔2秒鐘調用我們的回調方法。
你需要知道的c# Timer 的記憶體回收機制。