在某些情境下,我們需要限制同一支程式同時間只能執行一份,很直覺的想法是檢查Process清單,由程式名稱在清單中出現一次以上來判斷是否已有同名程式在執行。這個做法直覺有效,在大部分情境也適用,甚至在CodeProject上也不乏類似"教學範例",很自然地,這也一度是我愛用過的解法(誰沒有過去呢?);但是,檢查Process清單並非是最簡潔嚴謹的做法。(前述CodeProject文章的評比只有一顆星,留言中不乏負評,這故事告訴我們,爬文時莫急莫慌,至少先看看鄉民怎麼說再決定要不要抄)
ProcessName比對法的缺點不少,更完美的做法是善用Mutex機制,保哥對此有一篇詳盡說明。剛好讀到K. Scott Allen另一篇古老文章,對於ProcessName法的缺點及Mutex應用還有些額外闡述,簡單整理後,也來狗尾續貂一番。
Scott分析了幾個ProcessName法難以應付的"極端案例":
- 當人品差到極點,可能出現兩個程式同時開始執行,同時看到對方,同時關閉的情境。
- Process.GetProcesses()取得的是全機器的Process清單,難以滿足本機登入及遠端登入(Terminal Service)限制每個登入階段(Login Session)只執行一份的需求。(註: 應有方法依Session再分組,但得花點工)
- 若不幸系統中有另一個同名同姓的其他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名稱,可避免執行檔同名同姓的風險
staticstring appGuid = "{B19DAFCB-729C-43A6-8232-F3C31BB4E404}";
staticvoid 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
}
}
}
}