踩地雷-GC

  當一個地雷踩第二次的時候,就是以文字記錄下來的時候。

  重構是程式設計師每天的功課,我每一段時間都會針對code進行重構,整理程式也整理思緒,

最近重構看到了App.xaml中有著這段程式碼,在OnStartup裡。

private static Semaphore singleInstanceWatcher;
..........
protected override void OnStartup(StartupEventArgs e)
{
            bool createdNew = false;
            base.OnStartup(e);                        
            singleInstanceWatcher = new Semaphore(
                    0, // Initial count.
                    1, // Maximum count.
                    Assembly.GetExecutingAssembly().GetName().Name,
                    out createdNew);            
            if (!createdNew)
            {
                MessageQueueManager.PostMessage("instance already exists, wake up it");
                Process current = Process.GetCurrentProcess();
                bool found = false;
                foreach (Process process in
                     Process.GetProcessesByName(current.ProcessName))
                {
                    if (process.Id != current.Id)
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    singleInstanceWatcher.Release();
                    return;
                }
                // Terminate this process and gives the underlying operating 
                // system the specified exit code.
                Environment.Exit(-2);
            }
            .................
}

程式碼的目的很簡單,使用Semaphore來進行判斷同一支程式是否已經在執行,雖然運作很正常,但這明顯是一個需要重構的程式碼,因為這個動作是一個單元,至少必須移動到函式中,保持OnStartup的乾淨,更好的手法是把這個函式移動到另一個類別中,保持整個App.xaml的乾淨,不過第一階段當然是區域移動,所以改成下面這樣。



private bool CheckSingleInstance()
{
            var singleInstanceWatcher = new Semaphore(
                    0, // Initial count.
                    1, // Maximum count.
                    Assembly.GetExecutingAssembly().GetName().Name,
                    out var createdNew);
            if (!createdNew)
            {
                MessageQueueManager.PostMessage("instance already exists, wake up it");
                Process current = Process.GetCurrentProcess();
                bool found = false;
                foreach (Process process in
                     Process.GetProcessesByName(current.ProcessName))
                {
                    if (process.Id != current.Id)
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    singleInstanceWatcher.Release();
                    return true;
                }
                // Terminate this process and gives the underlying operating 
                // system the specified exit code.
                Environment.Exit(-2);
            }
            return false;
}
        
protected override void OnStartup(StartupEventArgs e)
{
            base.OnStartup(e);            
            if(CheckSingleInstance())
               return;
            .................
}

除了提取成函式,也把一些看起來不需要的變數改成區域宣告,但這個程式卻偶爾會不正常,常常無法偵測到上一個Instance而導致開啟兩個同樣的程式,一時間腦袋有點打結,我只是移動了函式而已啊。後來感覺好像似曾相識,以前似乎踩過同樣的雷。

原因在於Semaphore,當他是類別變數時,生命週期會跟著實體物件,這是App,所以會持續到應用程式結束,但CheckSingleInstance是區域函式,singleInstanceWatcher會被GC收走,此時使用Semaphore來判斷Single Instance的手法就會失敗,也因為GC啟動的時間不定,所以偶而正常。