在某些情境下,我們需要限制同一支程式同時間只能執行一份,很直覺的想法是檢查Process清單,由程式名稱在清單中出現一次以上來判斷是否已有同名程式在執行。這個做法直覺有效,在大部分情境也適用,甚至在CodeProject上也不乏類似"教學範例",很自然地,這也一度是我愛用過的解法(誰沒有過去呢?);但是,檢查Process清單並非是最簡潔嚴謹的做法。(前述CodeProject文章的評比只有一顆星,留言中不乏負評,這故事告訴我們,爬文時莫急莫慌,至少先看看鄉民怎麼說再決定要不要抄)

ProcessName比對法的缺點不少,更完美的做法是善用Mutex機制,保哥對此有一篇詳盡說明。剛好讀到K. Scott Allen另一篇古老文章,對於ProcessName法的缺點及Mutex應用還有些額外闡述,簡單整理後,也來狗尾續貂一番。

Scott分析了幾個ProcessName法難以應付的"極端案例":

  1. 當人品差到極點,可能出現兩個程式同時開始執行,同時看到對方,同時關閉的情境。
  2. Process.GetProcesses()取得的是全機器的Process清單,難以滿足本機登入及遠端登入(Terminal Service)限制每個登入階段(Login Session)只執行一份的需求。(註: 應有方法依Session再分組,但得花點工)
  3. 若不幸系統中有另一個同名同姓的其他Process,就杯具了。(註: 可以加上路徑比對避免)

因此,利用Mutex的強制排他性,可以輕易實現任何時候只有單一程序成功。而採用GUID或自訂唯一的Mutex名稱,則可防止同名同姓Process的干擾。

Scott文章另外提到了"using (Mutex)包住到程式結束為止"的技巧,目的在避免當後段程式未再使用Mutex物件被GC機制意外回收的極端案例。(註: Scott範例程式中的GC.Collect()是故意加上的,旨在觸發資源回收突顯問題,實務上除非記憶體耗用驚人,Mutex被意外回收的情境並不常出現,但改良版無疑更加完美)

以下是加了註解的簡單範例,展示如何實現程式防止多份同時執行: (移除@"Global\"可做到每個登入階段限定一份)

排版顯示純文字
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace SingleInstance
{
    class Program
    {
        //每支程式以不同GUID當成Mutex名稱,可避免執行檔同名同姓的風險
        static string appGuid = "{B19DAFCB-729C-43A6-8232-F3C31BB4E404}";
 
        static void Main(string[] args)
        {
           //如果要做到跨Session唯一,名稱可加入"Global\"前綴字
            //如此即使用多個帳號透過Terminal Service登入系統
            //整台機器也只能執行一份
            using (Mutex m = new Mutex(false, "Global\\" + appGuid))
            {
                //檢查是否同名Mutex已存在(表示另一份程式正在執行)
                if (!m.WaitOne(0, false))
                {
                    Console.WriteLine("Only one instance is allowed!");
                    return;
                }
 
                #region 程式處理邏輯
 
                Console.WriteLine("Press Enter to exit...");
                Console.ReadLine();
 
                //如果是Windows Form,Application.Run()要包在using Mutex範圍內
                  //以確保WinForm執行期間Mutex一直存在
 
                #endregion
            }
        }
    }
}

Comments

# by Houdini

黑大, 跟您請教一個問題: 最近寫一個 Winform 程式也是須要判斷唯一process, 但是因為某些需求必須由程式自動重新啟動 ( Application.restart() ), 但是這樣會造成唯一process判斷程式已執行而重啟失敗, (判斷應該是 restart 會先重啟新process 再關閉原 process, 導致同時兩個 process 產生 ) 不知道黑大是否有解決的方法? 謝謝!

# by Houdini

感謝黑大! 經測試後那篇是可以 work 的! 在 Application.Restart() 前先做 Program.Mutex.Close() 即可 ( Program.cs: public static Mutex Mutex; )

# by Morris

我之前有用過 Mutex 的方式來做... 但在本機開發時..(Windows 7)都可正常判斷.. 但程式移到 Win Srv 2008 R2 後就是不正常..判斷不出.. 當時也找不出為什麼.

Post a comment