このコードを使用して、プログラムの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が保持されるのではないかと心配しています。本当?
一般的にはい、これは動作します。しかし、悪魔は詳細にあります。
まず、finally
ブロック内のミューテックスを閉じます。そうしないと、プロセスが突然終了し、例外などのシグナル状態のままになる可能性があります。これにより、将来のプロセスインスタンスが起動できなくなります。
残念ながら、finally
ブロックを使用しても、mutexを解放せずにプロセスが終了する可能性に対処する必要があります。これは、ユーザーがTaskManagerを介してプロセスを強制終了した場合などに発生する可能性があります。コードには、2番目のプロセスがAbandonedMutexException
呼び出しでWaitOne
を取得できる競合状態があります。これには回復戦略が必要です。
Mutexクラスの詳細 を読むことをお勧めします。それを使用することは必ずしも簡単ではありません。
競合状態の可能性を拡張する:
次の一連のイベントが発生すると、アプリケーションの2番目のインスタンスがスローされます。
WaitOne
呼び出しの前に切り替えられます。AbanonedMutexException
を取得します。この目的で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\
プレフィックスを使用します。アプリケーションがユーザーごとの場合、適切に変形されたログオンユーザーの名前をイベント名に追加するだけです。
ミューテックスを使用できますが、最初にこれが本当に必要なものであることを確認してください。
「複数のインスタンスの回避」は明確に定義されていないためです。意味することができます
ミューテックスを使用すると、基本的に定義番号4を使用します。
私はこのメソッドを使用しますが、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に付属;-)
Windowsでは、プロセスを終了すると次の結果が生じます。
Mutexオブジェクトはカーネルオブジェクトであるため、プロセスが終了すると、プロセスによって保持されているオブジェクトはすべて閉じられます(とにかくWindowsで)。
ただし、CreateMutex()ドキュメントの次のビットに注意してください。
名前付きミューテックスを使用してアプリケーションを単一のインスタンスに制限している場合、悪意のあるユーザーが実行する前にこのミューテックスを作成して、アプリケーションの起動を妨げることができます。
はい、安全です。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());
}
}
これがコードスニペットです
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();
}
}
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");
}
}
ミューテックスベースのアプローチを使用する場合は、実際にローカルミューテックスを使用して 現在のユーザーのログインセッションのみにアプローチを制限する を使用する必要があります。また、堅牢なリソースの廃棄とミューテックスアプローチに関するリンクのその他の重要な注意事項にも注意してください。
1つの注意点は、mutexベースのアプローチでは、ユーザーが2番目のインスタンスを起動しようとしたときに、アプリの最初のインスタンスをアクティブにできないことです。
別の方法は、最初のインスタンスでFindWindowにPInvokeを実行し、その後にSetForegroundWindowを実行することです。別の方法は、名前でプロセスを確認することです:
Process[] processes = Process.GetProcessesByName("MyApp");
if (processes.Length != 1)
{
return;
}
これらの後者の代替案は両方とも、アプリの2つのインスタンスを同時に起動して、お互いを検出できるという仮想の競合状態を持っています。これは実際には起こりそうにありません-実際、テスト中にそれを実現することはできませんでした。
後者の2つの代替案のもう1つの問題は、ターミナルサービスを使用すると機能しないことです。
ここに私がこれにアプローチした方法があります
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