C#中 System.Threading.Timer 的回收問題


一. 問題來源

在我上家公司里,做停車軟件客戶端的時候,崗亭客戶端需要每隔一段時間,將本地時間和服務所在的電腦上的時間,和中央服務器上的本地時間進行同步。但是在實際運用的時候,打開客戶端除了開啟計時器(System.Threading.Timer)的時候會同步一次以外,之后就再也不會同步。

二. 關於 System.Threading.Timer

System.Threading.Timer 是一個比較特殊的對象,在程序還沒有執行到離開 System.Threading.Timer 的作用域的時候。如果發生一次 GC 的回收,那么在 Release 編譯的模式下,這個計時器會直接被當做垃圾而被 CLR 回收,造成無法正常進行定時操作。

當然在 Debug 編譯模式下,並不會發生這個問題。因為在 Debug 模式下,編譯器會添加相關的方法特性(編譯器會為程序集設置 DebuggingModes 的 DisableOptimizations 標志)來阻止 CLR 垃圾回收器在離開作用域之前回收它,將所有根的生存周期延長至方法結束。

值得一提的是,只要有一個根在引用它,對於其他的對象,並不會造成在離開作用域之前就被回收。所以 System.Threading.Timer 需要我們在某些特殊情況下區別於其他對象進行對待。

三. 解決 System.Threading.Timer 被提前回收的方法

解決這個被提前回收的方法主要有一下幾種:

1. 如果是在命令窗口中編譯,使用 " /Debug+ " ,於此同時不要去啟用 " /optimize "。如果是在VS中那么直接在上面的工具欄中選擇 " Debug " 即可。當然這種方法是有缺點的,就是不能處理 Release 編譯的程序;

操作步驟:

step 1:測試代碼(已經在應用進行精簡刪除 僅僅保留命名空間 using System )

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Threading.Timer timer = new System.Threading.Timer(ShowCuurentDataTime, null, 0, 1000);

            //讓timer不能離開當前作用域
            Console.ReadKey();
        }

        public static void ShowCuurentDataTime(Object obj)
        {
            Console.WriteLine("[System.Threading.Timer]當前時間:" + DateTime.Now);

            //強制GC回收
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        }
    }
}

step 2:命令窗口中編譯

  • 首先打開 " VS20XX 開發人員命令提示 ",可以在開始菜單的 VS 目錄下面找到,如下圖(用這個的原因是,這樣執行指令的時候,編輯環境由其准備好了),如下圖:


  • 然后打開之后,轉到 program.cs 所在目錄。然后執行命令
csc /t:exe /r:System.dll /Debug+ /optimize- program.cs

如下圖:


  • 之后在目錄下面就會有一個exe生成,打開之后可以正常處理計時事件。

step 3:如果是在 Visual Studio IDE 下那么直接在工具欄上把 Debug 選上編譯運行,在 bin\debug 目錄下的會有 exe ,如下圖:


注:

  • 當然要想 Releas 編譯的程序能使定時器正常運作,這種方法是不行的;
  • 使用其他類型的計時器,例如 System.Timers.Timer 或者是 System.Windows.Forms.Timer 。前者比較好用,后者會占用 UI 線程,不建議處理長時間的同步業務,如果寫成異步的,那么還是需要考慮異步的相關問題;
  • 在程序結束之前增加計時器的應用根。一種是在退出,或者釋放承載了該種計時器的對象的時候,對計時器進行顯式的引用釋放,如
timer.Dispose();

值得注意的是,並不能使用

t=null;

這樣來引用,因為編輯器會優化代碼,而忽略這一行。還有一種方式就是把 Timer 設置為 Timer 承載對象的一個字段或者屬性,讓 Timer 的生命周期得以延長到承載對象的生命周期結束:

【1】在程序退出之前,對計時器進行顯式的引用釋放的方法,如下圖:


【2】使用字段或者屬性,延長Timer生命周期,如下圖:

 

四. 項目工程下載

下載地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM