使用互斥锁来防止同一程序的多个实例运行安全吗?
我正在使用此代码来防止我的程序的第二个实例同时运行,是否安全?
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."); }
我担心,如果引发exception,并且应用程序崩溃,互斥量仍将保留。 真的吗?
一般来说,这是可行的。 然而魔鬼是在细节。
首先,你想closures在finally
一个块互斥。 否则,你的过程可能会突然终止,并使其处于信号状态,例外情况。 这将使未来的stream程实例无法启动。
不幸的是,即使有一个finally
块,你也必须处理一个进程将被终止而不释放互斥锁的可能性。 例如,如果用户通过TaskManager杀死进程,就会发生这种情况。 在你的代码中有一个竞争条件,允许第二个进程在WaitOne
调用中获得AbandonedMutexException
。 你需要一个恢复策略。
我鼓励你阅读互斥类的细节 。 使用它并不总是很简单。
扩大竞赛条件的可能性:
下面的一系列事件可能会导致应用程序的第二个实例抛出:
- 正常进程启动。
- 第二个进程启动并获取该互斥锁的句柄,但在
WaitOne
调用之前被切换。 - 过程#1突然终止。 互斥量不会被破坏,因为stream程#2有一个句柄。 而是设置为废弃状态。
- 第二个进程开始再次运行,并得到一个
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将为您closures事件,如果没有打开的句柄,则将其销毁。
增加 :为了pipe理会话,使用Local\
和Global\
前缀作为事件(或互斥体)名称。 如果您的应用程序是按用户进行操作的,只需在该事件名称后附加一个合适的已经login的用户名即可。
你可以使用互斥量,但首先确保这是真正你想要的。
因为“避免多个实例”没有明确的定义。 这可能意味着
- 避免多个实例在同一个用户会话中启动,无论用户会话有多less个桌面,但允许多个实例同时运行,以用于不同的用户会话。
- 避免在同一桌面上启动多个实例,但只要每个实例位于单独的桌面中,就允许多个实例运行。
- 避免为同一用户帐户启动多个实例,无论在此帐户下运行的桌面或会话数量是多less,而是允许多个实例同时运行,以便在不同的用户帐户下运行会话。
- 避免在同一台机器上启动多个实例。 这意味着无论任意数量的用户使用了多less个桌面,程序最多只能运行一个实例。
通过使用互斥量,基本上使用定义数字4。
我使用这种方法,我相信这是安全的,因为如果任何应用程序没有被持久化(如果最初不能创buildMutext,则终止应用程序),互斥体被销毁。 在“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/ }
上面的问题来自其他答案/评论中有关恶意程序能够坐在互斥体上的注释。 这里不用担心。 此外,在“本地”空间中创build的前缀互斥。 这可能是正确的。
请参阅: http : //ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx – 附带Jon Skeet 😉
在Windows上,终止进程具有以下结果:
- 进程中剩余的线程被标记为终止。
- 该进程分配的任何资源都被释放。
- 所有内核对象都closures。
- 进程代码从内存中删除。
- 进程退出代码已设置。
- 过程对象被发送信号。
互斥体对象是内核对象,所以当进程终止时(在Windows中),进程所持有的任何东西都是closures的。
但是,请注意CreateMutex()文档中的以下内容:
如果您正在使用已命名的互斥锁将应用程序限制为单个实例,则恶意用户可以在创build该互斥锁之前创build该互斥锁,并阻止您的应用程序启动。
是的,这是安全的,我会build议以下模式,因为你需要确保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(); } }
如果你想使用一个基于互斥体的方法,你应该真的使用一个本地的互斥体来限制方法只是curent用户的login会话 。 另外还要注意关于强大的资源处置与互斥方法的链接中的另一个重要警告。
需要注意的是,当用户尝试启动第二个实例时,基于互斥体的方法不允许您激活应用程序的第一个实例。
另一种方法是先调用FindWindow,然后再调用SetForegroundWindow。 另一种select是按名称检查你的过程:
Process[] processes = Process.GetProcessesByName("MyApp"); if (processes.Length != 1) { return; }
这两种替代scheme都有一个假设的竞争条件,其中应用程序的两个实例可以同时启动,然后互相检测。 实际上这不太可能发生 – 事实上,在testing过程中,我无法做到这一点。
这两种替代scheme的另一个问题是在使用terminal服务时它们将不起作用。
使用带有超时和安全设置的应用程序,避免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"); } }
这是我如何接近这一点
在Program类中:1.使用Process.GetCurrentProcess()获取应用程序的System.Diagnostics.Process。2.使用Process.GetProcessesByName(thisProcess.ProcessName)3,通过应用程序的当前名称收集打开的进程。检查每个process.Id对thisProcess.Id,如果一个实例已经打开,那么至less1将匹配名称,但不是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()或者什么