web-dev-qa-db-ja.com

Mutexを使用して、同じプログラムの複数のインスタンスが安全に実行されないようにしますか?

このコードを使用して、プログラムの2番目のインスタンスが同時に実行されるのを防ぎますが、安全ですか?

Mutex appSingleton = new System.Threading.Mutex(false, "MyAppSingleInstnceMutx");
if (appSingleton.WaitOne(0, false)) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
    appSingleton.Close();
} else {
    MessageBox.Show("Sorry, only one instance of MyApp is allowed.");
}

何かが例外をスローし、アプリがクラッシュしても、Mutexが保持されるのではないかと心配しています。本当?

44
Malfist

一般的にはい、これは動作します。しかし、悪魔は詳細にあります。

まず、finallyブロック内のミューテックスを閉じます。そうしないと、プロセスが突然終了し、例外などのシグナル状態のままになる可能性があります。これにより、将来のプロセスインスタンスが起動できなくなります。

残念ながら、finallyブロックを使用しても、mutexを解放せずにプロセスが終了する可能性に対処する必要があります。これは、ユーザーがTaskManagerを介してプロセスを強制終了した場合などに発生する可能性があります。コードには、2番目のプロセスがAbandonedMutexException呼び出しでWaitOneを取得できる競合状態があります。これには回復戦略が必要です。

Mutexクラスの詳細 を読むことをお勧めします。それを使用することは必ずしも簡単ではありません。


競合状態の可能性を拡張する:

次の一連のイベントが発生すると、アプリケーションの2番目のインスタンスがスローされます。

  1. 通常のプロセス起動。
  2. 2番目のプロセスが起動し、mutexのハンドルを取得しますが、WaitOne呼び出しの前に切り替えられます。
  3. プロセス#1は突然終了します。プロセス#2にハンドルがあるため、ミューテックスは破棄されません。代わりに、放棄された状態に設定されます。
  4. 2番目のプロセスは再び実行を開始し、AbanonedMutexExceptionを取得します。
40
JaredPar

この目的でWindowsイベントを使用する方がより一般的で便利です。例えば。

static EventWaitHandle s_event ;

bool created ;
s_event = new EventWaitHandle (false, 
    EventResetMode.ManualReset, "my program#startup", out created) ;
if (created) Launch () ;
else         Exit   () ;

プロセスが終了または終了すると、Windowsはイベントを閉じ、開いているハンドルが残っていない場合は破棄します。

追加:セッションを管理するには、イベント(またはミューテックス)名にLocal\およびGlobal\プレフィックスを使用します。アプリケーションがユーザーごとの場合、適切に変形されたログオンユーザーの名前をイベント名に追加するだけです。

59
Anton Tykhyy

ミューテックスを使用できますが、最初にこれが本当に必要なものであることを確認してください。

「複数のインスタンスの回避」は明確に定義されていないためです。意味することができます

  1. 同じユーザーセッションで複数のインスタンスが開始されるのを回避します。ユーザーセッションのデスクトップの数に関係なく、異なるユーザーセッションで複数のインスタンスを同時に実行できます。
  2. 同じデスクトップで複数のインスタンスを起動することは避けますが、各インスタンスが別々のデスクトップにある限り、複数のインスタンスを実行できます。
  3. このアカウントで実行されているデスクトップまたはセッションの数に関係なく、同じユーザーアカウントで開始される複数のインスタンスを回避しますが、異なるユーザーアカウントで実行されるセッションでは複数のインスタンスを同時に実行できます。
  4. 同じマシンで複数のインスタンスが起動するのを避けます。つまり、任意の数のユーザーが使用するデスクトップの数に関係なく、実行されるプログラムのインスタンスは最大で1つです。

ミューテックスを使用すると、基本的に定義番号4を使用します。

11
Stefan

私はこのメソッドを使用しますが、Mutexはアプリケーションによって保持されなくなると破棄されます(また、Mutextを最初に作成できない場合、アプリケーションは終了します)。これは、「AppDomain-processes」で同じように機能する場合と機能しない場合があります(下部のリンクを参照)。

// Make sure that appMutex has the lifetime of the code to guard --
// you must keep it from being collected (and the finalizer called, which
// will release the mutex, which is not good here!).
// You can also poke the mutex later.
Mutex appMutex;

// In some startup/initialization code
bool createdNew;
appMutex = new Mutex(true, "mutexname", out createdNew);
if (!createdNew) {
  // The mutex already existed - exit application.
  // Windows will release the resources for the process and the
  // mutex will go away when no process has it open.
  // Processes are much more cleaned-up after than threads :)
} else {
  // win \o/
}

上記は、ミューテックスに座ることができる悪意のあるプログラムに関する他の回答/コメントのメモに苦しんでいます。ここでは心配ありません。また、「ローカル」スペースに作成された接頭辞のないミューテックス。それはおそらくここで正しいことです。

参照: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx -Jon Skeetに付属;-)

9
user166390

Windowsでは、プロセスを終了すると次の結果が生じます。

  • プロセス内の残りのスレッドには、終了のマークが付けられます。
  • プロセスによって割り当てられたリソースはすべて解放されます。
  • すべてのカーネルオブジェクトが閉じられます。
  • プロセスコードがメモリから削除されます。
  • プロセス終了コードが設定されます。
  • プロセスオブジェクトが通知されます。

Mutexオブジェクトはカーネルオブジェクトであるため、プロセスが終了すると、プロセスによって保持されているオブジェクトはすべて閉じられます(とにかくWindowsで)。

ただし、CreateMutex()ドキュメントの次のビットに注意してください。

名前付きミューテックスを使用してアプリケーションを単一のインスタンスに制限している場合、悪意のあるユーザーが実行する前にこのミューテックスを作成して、アプリケーションの起動を妨げることができます。

3
Michael Burr

はい、安全です。Mutexが常に解放されるようにする必要があるため、次のパターンをお勧めします。

using( Mutex mutex = new Mutex( false, "mutex name" ) )
{
    if( !mutex.WaitOne( 0, true ) )
    {
        MessageBox.Show("Unable to run multiple instances of this program.",
                        "Error",  
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }
    else
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());                  
    }
}
2
arul

これがコードスニペットです

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
2
Siarhei Kuchuk

AbandonedMutexExceptionを回避して、タイムアウトとセキュリティ設定でアプリを使用します。カスタムクラスを使用しました。

private class SingleAppMutexControl : IDisposable
    {
        private readonly Mutex _mutex;
        private readonly bool _hasHandle;

        public SingleAppMutexControl(string appGuid, int waitmillisecondsTimeout = 5000)
        {
            bool createdNew;
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex = new Mutex(false, "Global\\" + appGuid, out createdNew, securitySettings);
            _hasHandle = false;
            try
            {
                _hasHandle = _mutex.WaitOne(waitmillisecondsTimeout, false);
                if (_hasHandle == false)
                    throw new System.TimeoutException();
            }
            catch (AbandonedMutexException)
            {
                _hasHandle = true;
            }
        }

        public void Dispose()
        {
            if (_mutex != null)
            {
                if (_hasHandle)
                    _mutex.ReleaseMutex();
                _mutex.Dispose();
            }
        }
    }

そしてそれを使用します:

    private static void Main(string[] args)
    {
        try
        {
            const string appguid = "{xxxxxxxx-xxxxxxxx}";
            using (new SingleAppMutexControl(appguid))
            {
                //run main app
                Console.ReadLine();
            }
        }
        catch (System.TimeoutException)
        {
            Log.Warn("Application already runned");
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Fatal Error on running");
        }
    }
0
vivlav

ミューテックスベースのアプローチを使用する場合は、実際にローカルミューテックスを使用して 現在のユーザーのログインセッションのみにアプローチを制限する を使用する必要があります。また、堅牢なリソースの廃棄とミューテックスアプローチに関するリンクのその他の重要な注意事項にも注意してください。

1つの注意点は、mutexベースのアプローチでは、ユーザーが2番目のインスタンスを起動しようとしたときに、アプリの最初のインスタンスをアクティブにできないことです。

別の方法は、最初のインスタンスでFindWindowにPInvokeを実行し、その後にSetForegroundWindowを実行することです。別の方法は、名前でプロセスを確認することです:

Process[] processes = Process.GetProcessesByName("MyApp");
if (processes.Length != 1)
{
    return;
} 

これらの後者の代替案は両方とも、アプリの2つのインスタンスを同時に起動して、お互いを検出できるという仮想の競合状態を持っています。これは実際には起こりそうにありません-実際、テスト中にそれを実現することはできませんでした。

後者の2つの代替案のもう1つの問題は、ターミナルサービスを使用すると機能しないことです。

0
RoadWarrior

ここに私がこれにアプローチした方法があります

Programクラスで:1. Process.GetCurrentProcess()を使用して、アプリケーションのSystem.Diagnostics.Processを取得します。2. Process.GetProcessesByName(thisProcess.ProcessName)を使用して、アプリケーションの現在の名前で開いているプロセスのコレクションをステップスルーします。各process.IdをthisProcess.Idに対してチェックし、インスタンスが既に開かれている場合、少なくとも1つは名前と一致しますがIDは一致しません。そうでない場合はインスタンスを開き続けます

using System.Diagnostics;

.....    

static void Main()
{
   Process thisProcess = Process.GetCurrentProcess();
   foreach(Process p in Process.GetProcessesByName(thisProcess.ProcessName))
   {
      if(p.Id != thisProcess.Id)
      {
         // Do whatever u want here to alert user to multiple instance
         return;
      }
   }
   // Continue on with opening application

これを完了するための素敵なタッチは、既に開かれているインスタンスをユーザーに提示することです。たぶん、彼らはそれが開いていることを知らなかったので、それがあったことを見せましょう。これを行うには、User32.dllを使用してWindowsメッセージングループにメッセージをブロードキャストし、カスタムメッセージを作成し、アプリにWndProcメソッドでリッスンさせます。このメッセージを取得すると、ユーザーに表示されますForm .Show()またはwhatnot

0
Wanabrutbeer