47

我正在使用此代码来防止我的程序的第二个实例同时运行,它安全吗?

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 仍将被保留。真的吗?

4

11 回答 11

65

为此目的使用 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\前缀。如果您的应用程序是每个用户的,只需将一个经过适当修改的登录用户名附加到事件名称。

于 2009-03-14T19:00:30.393 回答
43

一般来说,是的,这会起作用。然而,魔鬼在细节中。

首先,您要在一个finally块中关闭互斥锁。否则,您的进程可能会突然终止并使其处于信号状态,例如异常。这将使未来的流程实例无法启动。

不幸的是,即使有一个finally块,您也必须处理进程将在不释放互斥锁的情况下终止的可能性。例如,如果用户通过 TaskManager 终止进程,就会发生这种情况。您的代码中有一个竞争条件,允许第二个进程AbandonedMutexExceptionWaitOne调用中获得一个。您需要为此制定恢复策略。

我鼓励您阅读Mutex 类的详细信息。使用它并不总是那么简单。


扩展竞争条件的可能性:

可能会发生以下事件序列,这将导致应用程序的第二个实例抛出:

  1. 正常进程启动。
  2. 第二个进程启动并获取互斥锁的句柄,但在WaitOne调用之前被关闭。
  3. 进程#1 突然终止。互斥体没有被破坏,因为进程#2 有一个句柄。相反,它被设置为废弃状态。
  4. 第二个进程再次开始运行并获得一个AbanonedMutexException.
于 2009-03-14T18:57:32.087 回答
12

您可以使用互斥锁,但首先要确保这确实是您想要的。

因为“避免多个实例”没有明确定义。这可能意味着

  1. 避免在同一个用户会话中启动多个实例,无论该用户会话有多少桌面,但允许多个实例针对不同的用户会话同时运行。
  2. 避免在同一个桌面上启动多个实例,但允许多个实例运行,只要每个实例都在单独的桌面上。
  3. 避免为同一用户帐户启动多个实例,无论存在多少在此帐户下运行的桌面或会话,但允许多个实例同时运行以在不同用户帐户下运行的会话。
  4. 避免在同一台机器上启动多个实例。这意味着无论任意数量的用户使用多少桌面,最多只能有一个程序实例在运行。

通过使用互斥体,您基本上使用的是定义编号 4。

于 2009-03-14T19:02:38.610 回答
11

我使用这种方法,我相信它是安全的,因为如果不再被任何应用程序持有,互斥锁将被销毁(如果应用程序最初无法创建互斥锁,则应用程序将被终止)。这在“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 附带 ;-)

于 2010-04-13T06:36:35.113 回答
3

在 Windows 上,终止进程会产生以下结果:

  • 进程中的任何剩余线程都被标记为终止。
  • 进程分配的任何资源都被释放。
  • 所有内核对象都已关闭。
  • 进程代码从内存中删除。
  • 进程退出代码已设置。
  • 过程对象发出信号。

互斥对象是内核对象,因此当进程终止时(无论如何在 Windows 中),进程持有的任何对象都会关闭。

但是,请注意 CreateMutex() 文档中的以下内容:

如果您使用命名互斥锁将应用程序限制为单个实例,则恶意用户可以在您创建此互斥锁之前创建此互斥锁并阻止您的应用程序启动。

于 2009-03-14T19:01:11.483 回答
3

是的,这很安全,我建议使用以下模式,因为您需要确保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());                  
    }
}
于 2009-03-14T19:02:31.887 回答
2

这是代码片段

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();
    }
}
于 2016-07-13T20:06:39.010 回答
1

这是我的处理方式

在程序类中:

  1. 使用 Process.GetCurrentProcess() 获取您的应用程序的 System.Diagnostics.Process

  2. 使用 Process.GetProcessesByName(thisProcess.ProcessName) 逐步浏览具有应用程序当前名称的打开进程集合

  3. 对照 thisProcess.Id 检查每个 process.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() 之类的。

于 2015-12-14T18:21:07.090 回答
0

如果您想使用基于互斥锁的方法,您应该真正使用本地互斥锁来将方法限制为仅当前用户的登录会话。还要注意该链接中关于使用互斥锁方法进行强大的资源处理的另一个重要警告。

需要注意的是,基于互斥锁的方法不允许您在用户尝试启动第二个实例时激活应用程序的第一个实例。

另一种方法是 PInvoke 到 FindWindow,然后在第一个实例上使用 SetForegroundWindow。另一种选择是按名称检查您的进程:

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

后两种选择都有一个假设的竞争条件,其中应用程序的两个实例可以同时启动,然后相互检测。这在实践中不太可能发生 - 事实上,在测试期间我无法实现。

后两种选择的另一个问题是它们在使用终端服务时不起作用。

于 2009-03-14T19:17:53.927 回答
0

使用具有超时和安全设置的应用程序,避免 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");
        }
    }
于 2015-07-28T06:45:08.313 回答
0

重要的是要记住,可以通过 Microsoft sysinternals 中的 handle.exe 等程序轻松查看此互斥体\事件,然后如果使用常量名称,邪恶的人可能会使用它来阻止您的应用程序启动。

以下是微软关于安全替代品建议的一些引用:

但是,恶意用户可以在您创建此互斥锁之前阻止您的应用程序启动。为防止出现这种情况,请创建一个随机命名的互斥体并将名称存储起来,以便只能由授权用户获取。或者,您可以为此目的使用文件。要将您的应用程序限制为每个用户一个实例,请在用户的配置文件目录中创建一个锁定文件。

取自这里: https ://docs.microsoft.com/en-us/sysinternals/downloads/handle

于 2020-04-10T16:16:51.540 回答