2017年8月17日 星期四

C#,利用Mutex實現應用程式的單實例運行

C#,利用Mutex實現應用程式的單實例運行
C#,利用Mutex實現應用程式的單實例運行

System.Threading.Mutex :同步基元,它只向一個執行緒授予對共用資源的獨佔訪問權。[MSDN]
實現原理: 在程式啟動時,請求一個互斥體,如果能獲取對指定互斥的訪問權,就繼續運行程式,否則就退出程式。
測試代碼: 
class Test
     {
         /// <summary>
         /// 應用程式的主入口點。
         /// </summary>
          [STAThread]
         static void Main(string[] args)
         {
              bool flag=false;
              System.Threading.Mutex mutex=new System.Threading.Mutex(true,"Test",out flag);
              //第一個參數:true--給調用執行緒賦予互斥體的初始所屬權
              //第一個參數:互斥體的名稱
              //第三個參數:返回值,如果調用執行緒已被授予互斥體的初始所屬權,則返回true

//isAppRunning = false表示已執行,沒有拿到權仗,若拿到權杖則為true

              if(flag)
              {
                   Console.Write("Running");
              }
              else
              {
                   Console.Write("Another is Running");
                   System.Threading.Thread.Sleep(5000);//執行緒掛起5秒鐘
           //請勿使用this.close(); 會有錯誤發生
                   Environment.Exit(1);//退出程式
              }
              Console.ReadLine();
         }

運行結果:
第一次運行,輸出"Running"。
不關閉第一次運行的程式, 進行第二次運行,輸出"Another is Running",五秒鐘後,程式自動退出。

[C#]使用Mutex實現單一程式執行個體的注意事項
[C#]使用Mutex實現單一程式執行個體的注意事項
相信大家都知道在.NET程式中若要實現單一程式執行個體,一般來說有幾種方法,像是去判斷是否已經有開啟的Process是相同的程式、用Mutex與Semaphore之類的技術來判斷是否程式正在開啟。但是很多網路上的文章都忽略了在用Mutex實現單一程式執行個體時,其實會有些必須要注意的地方,導致於在實際運用上沒有發揮到該有的效果。

以一個簡單的例子來看,一般我們在網路上常看到的使用方式大概就像下面的程式碼片段類似,建構Mutex時就會回傳該Mutex是否已經存在,利用該回傳值來決定程式應該繼續開啟還是關閉。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApplication10
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

Boolean bCreatedNew;

//Create a new mutex using specific mutex name
Mutex m = new Mutex(false, "myUniqueName", out bCreatedNew);

if (bCreatedNew)
Application.Run(new Form1());
}
}
}

這樣的程式到底有什麼樣的問題呢?有興趣的可以試著建置Release的程式看看,其實這樣的程式在某些情況下會在Release模式下失效,Debug的運作卻是正常的,若試不出來的可以再加上個GC.Collect試試,會更容易重現。
...
Boolean bCreatedNew;

//Create a new mutex using specific mutex name
Mutex m = new Mutex(false, "myUniqueName", out bCreatedNew);

GC.Collect();

if (bCreatedNew)
Application.Run(new Form1());
...

之所以會有這樣的問題,是因為Mutex在Release模式下被GC給回收了,而Debug模式下因為便於開發人員除錯,據說有將GC的周期給拉長,所以不容易重現。那這樣的問題要怎麼樣解決呢?這邊筆者有整理了幾種方法。

一個方法就是把Mutex給拉出來成為類別成員。
...
static Mutex m;

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

Boolean bCreatedNew;

//Create a new mutex using specific mutex name
m = new Mutex(false, "myUniqueName", out bCreatedNew);
GC.Collect();

if (bCreatedNew)
Application.Run(new Form1());
}
...

另一個方法就是讓Mutex不要被GC回收掉,像是在程式最後明確呼叫Dispose,讓GC知道該Mutex仍在使用。
...
Boolean bCreatedNew;

//Create a new mutex using specific mutex name
Mutex m = new Mutex(false, "myUniqueName", out bCreatedNew);

GC.Collect();

if (bCreatedNew)
Application.Run(new Form1());

m.Dispose();
...

也可以用using或是try...finally之類的語法將Mutex給hold住。
...
Boolean bCreatedNew;

//Create a new mutex using specific mutex name
using (Mutex m = new Mutex(false, "myUniqueName", out bCreatedNew))
{
GC.Collect();

if (bCreatedNew)
Application.Run(new Form1());
}
...

或是把Mutex的用法寫的比較正規一點,加上WaitOne與ReleaseMutex去明確控制Mutex的作用範圍。
...
Boolean bCreatedNew;

//Create a new mutex using specific mutex name
Mutex m = new Mutex(false, "myUniqueName", out bCreatedNew);

m.WaitOne();
GC.Collect();

if (bCreatedNew)
Application.Run(new Form1());

m.ReleaseMutex();
...

沒有留言:

張貼留言