回答:
正しく理解するのは非常に難しいので、これが確実に存在するようにしたいと思います。
using System.Runtime.InteropServices; //GuidAttribute
using System.Reflection; //Assembly
using System.Threading; //Mutex
using System.Security.AccessControl; //MutexAccessRule
using System.Security.Principal; //SecurityIdentifier
static void Main(string[] args)
{
// get application GUID as defined in AssemblyInfo.cs
string appGuid =
((GuidAttribute)Assembly.GetExecutingAssembly().
GetCustomAttributes(typeof(GuidAttribute), false).
GetValue(0)).Value.ToString();
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
// Need a place to store a return value in Mutex() constructor call
bool createdNew;
// edited by Jeremy Wiebe to add example of setting up security for multi-user usage
// edited by 'Marc' to work also on localized systems (don't use just "Everyone")
var allowEveryoneRule =
new MutexAccessRule( new SecurityIdentifier( WellKnownSidType.WorldSid
, null)
, MutexRights.FullControl
, AccessControlType.Allow
);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
// edited by MasonGZhwiti to prevent race condition on security settings via VanNguyen
using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
{
// edited by acidzombie24
var hasHandle = false;
try
{
try
{
// note, you may want to time out here instead of waiting forever
// edited by acidzombie24
// mutex.WaitOne(Timeout.Infinite, false);
hasHandle = mutex.WaitOne(5000, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access");
}
catch (AbandonedMutexException)
{
// Log the fact that the mutex was abandoned in another process,
// it will still get acquired
hasHandle = true;
}
// Perform your work here.
}
finally
{
// edited by acidzombie24, added if statement
if(hasHandle)
mutex.ReleaseMutex();
}
}
}
exitContext = false
何かしmutex.WaitOne(5000, false)
ますか?CoreCLRでアサートが発生するだけの可能性があるようです。2。Mutex
コンストラクターで誰かが疑問に思っている場合、理由initiallyOwned
はこのMSDNの記事でfalse
部分的に説明されています。
受け入れられた回答を使用して、ヘルパークラスを作成します。これにより、Lockステートメントを使用するのと同じように使用できます。共有したいと思っただけです。
使用する:
using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
//Only 1 of these runs at a time
RunSomeStuff();
}
そしてヘルパークラス:
class SingleGlobalInstance : IDisposable
{
//edit by user "jitbit" - renamed private fields to "_"
public bool _hasHandle = false;
Mutex _mutex;
private void InitMutex()
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value;
string mutexId = string.Format("Global\\{{{0}}}", appGuid);
_mutex = new Mutex(false, mutexId);
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
_mutex.SetAccessControl(securitySettings);
}
public SingleGlobalInstance(int timeOut)
{
InitMutex();
try
{
if(timeOut < 0)
_hasHandle = _mutex.WaitOne(Timeout.Infinite, false);
else
_hasHandle = _mutex.WaitOne(timeOut, false);
if (_hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
}
catch (AbandonedMutexException)
{
_hasHandle = true;
}
}
public void Dispose()
{
if (_mutex != null)
{
if (_hasHandle)
_mutex.ReleaseMutex();
_mutex.Close();
}
}
}
< 0
代わりにチェックしてください<= 0
。
_mutex.Close()
代わりに使用すると効果があることがわかりまし_mutex.Dispose()
た。エラーは、基になるWaitHandleを破棄しようとしたことが原因でした。Mutex.Close()
基になるリソースを破棄します。
2つの異なるユーザーの下で実行されている2つのプロセスが同時にmutexを初期化しようとすると、受け入れられた回答に競合状態が発生します。最初のプロセスがミューテックスを初期化した後、最初のプロセスがアクセスルールを全員に設定する前に2番目のプロセスがミューテックスを初期化しようとすると、2番目のプロセスによって不正な例外がスローされます。
正解については以下をご覧ください:
using System.Runtime.InteropServices; //GuidAttribute
using System.Reflection; //Assembly
using System.Threading; //Mutex
using System.Security.AccessControl; //MutexAccessRule
using System.Security.Principal; //SecurityIdentifier
static void Main(string[] args)
{
// get application GUID as defined in AssemblyInfo.cs
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
bool createdNew;
// edited by Jeremy Wiebe to add example of setting up security for multi-user usage
// edited by 'Marc' to work also on localized systems (don't use just "Everyone")
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
{
// edited by acidzombie24
var hasHandle = false;
try
{
try
{
// note, you may want to time out here instead of waiting forever
// edited by acidzombie24
// mutex.WaitOne(Timeout.Infinite, false);
hasHandle = mutex.WaitOne(5000, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access");
}
catch (AbandonedMutexException)
{
// Log the fact the mutex was abandoned in another process, it will still get aquired
hasHandle = true;
}
// Perform your work here.
}
finally
{
// edited by acidzombie24, added if statemnet
if(hasHandle)
mutex.ReleaseMutex();
}
}
}
別のインスタンスがすでに実行されている場合、この例は5秒後に終了します。
// unique id for global mutex - Global prefix means it is global to the machine
const string mutex_id = "Global\\{B1E7934A-F688-417f-8FCB-65C3985E9E27}";
static void Main(string[] args)
{
using (var mutex = new Mutex(false, mutex_id))
{
try
{
try
{
if (!mutex.WaitOne(TimeSpan.FromSeconds(5), false))
{
Console.WriteLine("Another instance of this program is running");
Environment.Exit(0);
}
}
catch (AbandonedMutexException)
{
// Log the fact the mutex was abandoned in another process, it will still get aquired
}
// Perform your work here.
}
finally
{
mutex.ReleaseMutex();
}
}
}
MutexもWinApi CreateMutex()も私にはうまくいきません。
代替ソリューション:
static class Program
{
[STAThread]
static void Main()
{
if (SingleApplicationDetector.IsRunning()) {
return;
}
Application.Run(new MainForm());
SingleApplicationDetector.Close();
}
}
そしてSingleApplicationDetector
:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
public static class SingleApplicationDetector
{
public static bool IsRunning()
{
string guid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
var semaphoreName = @"Global\" + guid;
try {
__semaphore = Semaphore.OpenExisting(semaphoreName, SemaphoreRights.Synchronize);
Close();
return true;
}
catch (Exception ex) {
__semaphore = new Semaphore(0, 1, semaphoreName);
return false;
}
}
public static void Close()
{
if (__semaphore != null) {
__semaphore.Close();
__semaphore = null;
}
}
private static Semaphore __semaphore;
}
Mutexの代わりにSemaphoreを使用する理由:
MutexクラスはスレッドIDを適用するため、ミューテックスはそれを取得したスレッドによってのみ解放できます。対照的に、SemaphoreクラスはスレッドIDを強制しません。
Semaphore.OpenExisting
との間の可能な競合状態new Semaphore
。
例によって学ぶことが最も役立つ場合があります。このコンソールアプリケーションを3つの異なるコンソールウィンドウで実行します。最初に実行したアプリケーションが最初にミューテックスを取得し、他の2つが順番を待っていることがわかります。次に、最初のアプリケーションでEnterキーを押します。アプリケーション2がミューテックスを取得して実行を継続していることがわかりますが、アプリケーション3はその順番を待っています。アプリケーション2でEnterキーを押すと、アプリケーション3が続行することがわかります。これは、例としてファイルへの書き込みのように、1つのスレッド(この場合はプロセス)によってのみ実行されるコードのセクションを保護するミューテックスの概念を示しています。
using System;
using System.Threading;
namespace MutexExample
{
class Program
{
static Mutex m = new Mutex(false, "myMutex");//create a new NAMED mutex, DO NOT OWN IT
static void Main(string[] args)
{
Console.WriteLine("Waiting to acquire Mutex");
m.WaitOne(); //ask to own the mutex, you'll be queued until it is released
Console.WriteLine("Mutex acquired.\nPress enter to release Mutex");
Console.ReadLine();
m.ReleaseMutex();//release the mutex so other processes can use it
}
}
}
グローバルMutexは、アプリケーションのインスタンスが1つだけであることを保証するだけではありません。個人的には、Microsoft.VisualBasicを使用して、シングルインスタンスのWPFアプリケーションを作成する正しい方法は何ですか?(Dale Raganの回答)...新しいアプリケーションの起動時に受け取った引数を最初の単一インスタンスアプリケーションに渡す方が簡単だと思いました。
しかし、このスレッドの以前のコードに関しては、ロックをかけたいときに毎回ミューテックスを作成したくないです。単一インスタンスのアプリケーションでは問題ないかもしれませんが、他の使用法では、やりすぎだと思われます。
そのため、代わりにこの実装をお勧めします。
使用法:
static MutexGlobal _globalMutex = null;
static MutexGlobal GlobalMutexAccessEMTP
{
get
{
if (_globalMutex == null)
{
_globalMutex = new MutexGlobal();
}
return _globalMutex;
}
}
using (GlobalMutexAccessEMTP.GetAwaiter())
{
...
}
Mutexグローバルラッパー:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
namespace HQ.Util.General.Threading
{
public class MutexGlobal : IDisposable
{
// ************************************************************************
public string Name { get; private set; }
internal Mutex Mutex { get; private set; }
public int DefaultTimeOut { get; set; }
public Func<int, bool> FuncTimeOutRetry { get; set; }
// ************************************************************************
public static MutexGlobal GetApplicationMutex(int defaultTimeOut = Timeout.Infinite)
{
return new MutexGlobal(defaultTimeOut, ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value);
}
// ************************************************************************
public MutexGlobal(int defaultTimeOut = Timeout.Infinite, string specificName = null)
{
try
{
if (string.IsNullOrEmpty(specificName))
{
Name = Guid.NewGuid().ToString();
}
else
{
Name = specificName;
}
Name = string.Format("Global\\{{{0}}}", Name);
DefaultTimeOut = defaultTimeOut;
FuncTimeOutRetry = DefaultFuncTimeOutRetry;
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
Mutex = new Mutex(false, Name, out bool createdNew, securitySettings);
if (Mutex == null)
{
throw new Exception($"Unable to create mutex: {Name}");
}
}
catch (Exception ex)
{
Log.Log.Instance.AddEntry(Log.LogType.LogException, $"Unable to create Mutex: {Name}", ex);
throw;
}
}
// ************************************************************************
/// <summary>
///
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public MutexGlobalAwaiter GetAwaiter(int timeOut)
{
return new MutexGlobalAwaiter(this, timeOut);
}
// ************************************************************************
/// <summary>
///
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
public MutexGlobalAwaiter GetAwaiter()
{
return new MutexGlobalAwaiter(this, DefaultTimeOut);
}
// ************************************************************************
/// <summary>
/// This method could either throw any user specific exception or return
/// true to retry. Otherwise, retruning false will let the thread continue
/// and you should verify the state of MutexGlobalAwaiter.HasTimedOut to
/// take proper action depending on timeout or not.
/// </summary>
/// <param name="timeOutUsed"></param>
/// <returns></returns>
private bool DefaultFuncTimeOutRetry(int timeOutUsed)
{
// throw new TimeoutException($"Mutex {Name} timed out {timeOutUsed}.");
Log.Log.Instance.AddEntry(Log.LogType.LogWarning, $"Mutex {Name} timeout: {timeOutUsed}.");
return true; // retry
}
// ************************************************************************
public void Dispose()
{
if (Mutex != null)
{
Mutex.ReleaseMutex();
Mutex.Close();
}
}
// ************************************************************************
}
}
ウェイター
using System;
namespace HQ.Util.General.Threading
{
public class MutexGlobalAwaiter : IDisposable
{
MutexGlobal _mutexGlobal = null;
public bool HasTimedOut { get; set; } = false;
internal MutexGlobalAwaiter(MutexGlobal mutexEx, int timeOut)
{
_mutexGlobal = mutexEx;
do
{
HasTimedOut = !_mutexGlobal.Mutex.WaitOne(timeOut, false);
if (! HasTimedOut) // Signal received
{
return;
}
} while (_mutexGlobal.FuncTimeOutRetry(timeOut));
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_mutexGlobal.Mutex.ReleaseMutex();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~MutexExAwaiter()
// {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}
AWonnedMutexExceptionを引き起こす可能性があるため、WaitOneを使用しないソリューション(WPF用)。このソリューションでは、createdNewブール値を返すMutexコンストラクターを使用して、mutexがすでに作成されているかどうかを確認します。また、GetType()。GUIDを使用するため、実行可能ファイルの名前を変更しても複数のインスタンスは許可されません。
グローバルミューテックスとローカルミューテックス:https : //docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.8
private Mutex mutex;
private bool mutexCreated;
public App()
{
string mutexId = $"Global\\{GetType().GUID}";
mutex = new Mutex(true, mutexId, out mutexCreated);
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
if (!mutexCreated)
{
MessageBox.Show("Already started!");
Shutdown();
}
}
MutexはIDisposableを実装しているため、自動的に解放されますが、完全を期すために、disposeを呼び出します。
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
mutex.Dispose();
}
すべてを基本クラスに移動し、受け入れられた回答からallowEveryoneRuleを追加します。OSによって自動的にリリースされるため、本当に必要なようには見えませんが、ReleaseMutexも追加しました(アプリケーションがクラッシュし、ReleaseMutexを呼び出さない場合は、再起動する必要がありますか?)。
public class SingleApplication : Application
{
private Mutex mutex;
private bool mutexCreated;
public SingleApplication()
{
string mutexId = $"Global\\{GetType().GUID}";
MutexAccessRule allowEveryoneRule = new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl,
AccessControlType.Allow);
MutexSecurity securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
// initiallyOwned: true == false + mutex.WaitOne()
mutex = new Mutex(initiallyOwned: true, mutexId, out mutexCreated, securitySettings); }
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
if (mutexCreated)
{
try
{
mutex.ReleaseMutex();
}
catch (ApplicationException ex)
{
MessageBox.Show(ex.Message, ex.GetType().FullName, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
mutex.Dispose();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
if (!mutexCreated)
{
MessageBox.Show("Already started!");
Shutdown();
}
}
}
using
しcreatedNew
て、mutex.Dispose()
内部をチェックして追加することもできますfinally
。はっきりとは説明できませんが(理由はわかりません)になってからmutex.WaitOne
戻ってきtrue
たときの状況にcreatedNew
なりましたfalse
(現在のミューテックスを取得してからAppDomain
新しいAppDomain
コードをロードして同じコードを実行しました)その中で)。