C# 是否使用互斥锁来防止同一程序的多个实例安全运行?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/646480/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 11:37:26  来源:igfitidea点击:

Is using a Mutex to prevent multiple instances of the same program from running safe?

c#winformsmutexsingle-instance

提问by Malfist

I'm using this code to prevent a second instance of my program from running at the same time, is it safe?

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

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.");
}

I'm worried that if something throws an exception and the app crashes that the Mutex will still be held. Is that true?

我担心如果出现异常并且应用程序崩溃,互斥锁仍将被保留。真的吗?

采纳答案by JaredPar

In general yes this will work. However the devil is in the details.

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

Firstly you want to close the mutex in a finallyblock. Otherwise your process could abruptly terminate and leave it in a signaled state, like an exception. That would make it so that future process instances would not be able to start up.

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

Unfortunately though, even with a finallyblock you must deal with the potential that a process will be terminated without freeing up the mutex. This can happen for instance if a user kills the process through TaskManager. There is a race condition in your code that would allow for a second process to get an AbandonedMutexExceptionin the WaitOnecall. You'll need a recovery strategy for this.

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

I encourage you to read up on the details of the Mutex class. Using it is not always simple.

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



Expanding upon the race condition possibility:

扩展竞争条件的可能性:

The following sequence of events can occur which would cause a second instance of the application to throw:

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

  1. Normal process startup.
  2. Second process starts up and aquires a handle to the mutex but is switched out before the WaitOnecall.
  3. Process #1 is abruptly terminated. The mutex is not destroyed because process #2 has a handle. It is instead set to an abandoned state.
  4. The second process starts running again and gets an AbanonedMutexException.
  1. 正常进程启动。
  2. 第二个进程启动并获取互斥锁的句柄,但在WaitOne调用之前被关闭。
  3. 进程#1 突然终止。互斥体不会被销毁,因为进程 #2 有一个句柄。相反,它被设置为废弃状态。
  4. 第二个进程再次开始运行并得到一个AbanonedMutexException.

回答by Anton Tykhyy

It is more usual and convenient to use Windows events for this purpose. E.g.

为此目的使用 Windows 事件更为常见和方便。例如

static EventWaitHandle s_event ;

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

When your process exits or terminates, Windows will close the event for you, and destroy it if no open handles remain.

当您的进程退出或终止时,Windows 将为您关闭该事件,如果没有打开的句柄,则将其销毁。

Added: to manage sessions, use Local\and Global\prefixes for the event (or mutex) name. If your application is per-user, just append a suitably mangled logged-on user's name to the event name.

添加:管理会话,事件(或互斥)名称的使用Local\Global\前缀。如果您的应用程序是每个用户的,只需将适当修改的登录用户名附加到事件名称。

回答by Michael Burr

On Windows, terminating a process has the following results:

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

  • Any remaining threads in the process are marked for termination.
  • Any resources allocated by the process are freed.
  • All kernel objects are closed.
  • The process code is removed from memory.
  • The process exit code is set.
  • The process object is signaled.
  • 进程中的任何剩余线程都被标记为终止。
  • 进程分配的任何资源都将被释放。
  • 所有内核对象都已关闭。
  • 进程代码从内存中删除。
  • 进程退出代码已设置。
  • 进程对象被通知。

Mutex objects are kernel objects, so any held by a process are closed when the process terminates (in Windows anyway).

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

But, note the following bit from the CreateMutex() docs:

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

If you are using a named mutex to limit your application to a single instance, a malicious user can create this mutex before you do and prevent your application from starting.

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

回答by arul

Yes, it's safe, I'd suggest the following pattern, because you need to make sure that the Mutexgets always released.

是的,这是安全的,我建议采用以下模式,因为您需要确保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());                  
    }
}

回答by Stefan

You can use a mutex, but first make sure that this is really what you want.

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

Because "avoiding multiple instances" is not clearly defined. It can mean

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

  1. Avoiding multiple instances started in the same user session, no matter how many desktops that user session has, but allowing multiple instances to run concurrently for different user sessions.
  2. Avoiding multiple instances started in the same desktop, but allowing multiple instances to run as long as each one is in a separate desktop.
  3. Avoiding multiple instances started for the same user account, no matter how many desktops or sessions running under this account exist, but allowing multiple instances to run concurrently for sessions running under a different user account.
  4. Avoiding multiple instances started on the same machine. This means that no matter how many desktops are used by an arbitrary number of users, there can be at most one instance of the program running.
  1. 避免在同一个用户会话中启动多个实例,无论该用户会话有多少个桌面,而是允许多个实例为不同的用户会话同时运行。
  2. 避免在同一个桌面中启动多个实例,但允许多个实例运行,只要每个实例都在单独的桌面中。
  3. 避免为同一个用户帐户启动多个实例,无论存在多少个在此帐户下运行的桌面或会话,但允许多个实例为在不同用户帐户下运行的会话同时运行。
  4. 避免在同一台机器上启动多个实例。这意味着无论任意数量的用户使用多少个桌面,最多只能运行一个程序实例。

By using a mutex, you're basically using the define number 4.

通过使用互斥锁,您基本上是在使用定义数字 4。

回答by HTTP 410

If you want to use a mutex-based approach, you should really use a local mutex to restrict the approach to just the curent user's login session. And also note the other important caveat in that link about robust resource disposal with the mutex approach.

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

One caveat is that a mutex-based approach doesn't allow you to activate the first instance of the app when a user tries to start a second instance.

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

An alternative is to PInvoke to FindWindow followed by SetForegroundWindow on the first instance. Another alternative is to check for your process by name:

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

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

Both of these latter alternatives have a hypothetical race condition where two instances of the app could be started simultaneously and then detect each other. This is unlikely to happen in practice - in fact, during testing I couldn't make it happen.

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

Another issue with these two latter alternatives is that they won't work when using Terminal Services.

后两个替代方案的另一个问题是它们在使用终端服务时不起作用。

回答by HTTP 410

I use this method, I believe it to be safe because the Mutex is destroyed if no long held by any application (and applications are terminated if if they can't initially create the Mutext). This may or may not work identically in 'AppDomain-processes' (see link at bottom):

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

The above suffers from from notes in other answers/comments about malicious programs being able to sit on a mutex. Not a concern here. Also, unprefixed mutex created in "Local" space. It's probably the-right-thing here.

以上受到其他答案/评论中关于恶意程序能够坐在互斥锁上的注释的影响。这里不关心。此外,在“本地”空间中创建了无前缀的互斥锁。这可能是正确的。

See: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx-- comes with Jon Skeet ;-)

请参阅:http: //ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx——与 Jon Skeet 一起提供;-)

回答by vivlav

Use app with timeout and security settings with avoiding AbandonedMutexException. I used my custom class:

使用带有超时和安全设置的应用程序,避免 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();
            }
        }
    }

and use it:

并使用它:

    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");
        }
    }

回答by Wanabrutbeer

Here is how I have approached this

这是我如何处理这个

In the Program class: 1. Get a System.Diagnostics.Process of YOUR application using Process.GetCurrentProcess() 2. Step through the collection of open processes with the current name of your application using Process.GetProcessesByName(thisProcess.ProcessName) 3. Check each process.Id against thisProcess.Id and if an instance is already opened then at least 1 will match name but not Id, otherwise continue opening instance

在 Program 类中: 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

A nice touch to finish this off would be to present the already opened instance to the user, chances are they didn't know it was open, so let's show them it was. To do this I use User32.dll to Broadcast a message into the Windows messaging loop, a custom message, and I have my app listen for it in the WndProc method, and if it gets this message, it presents itself to the user, Form.Show() or whatnot

完成此操作的一个很好的方法是向用户展示已经打开的实例,很可能他们不知道它是打开的,所以让我们向他们展示它是。为此,我使用 User32.dll 将一条消息广播到 Windows 消息传递循环中,一条自定义消息,并且我让我的应用程序在 WndProc 方法中侦听它,如果它收到此消息,它会将自己呈现给用户 Form .Show() 或诸如此类

回答by Siarhei Kuchuk

Here's the code snippet

这是代码片段

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();
    }
}