【问题标题】:How to determine if a previous instance of my application is running?如何确定我的应用程序的先前实例是否正在运行?
【发布时间】:2009-04-22 19:32:55
【问题描述】:

我有一个 C# 控制台应用程序,我在其中运行各种神秘的自动化任务。我很清楚这应该是一个真正的 Windows 服务,因为它需要连续运行,但我现在不想这样做。 (所以,不要建议作为答案)。

与此同时,我需要一些示例 C# 代码来确定是否已经有一个应用程序实例正在运行。

在旧的 VB6.0 时代,我会使用 App.PrevInstance()

我希望能够在我的 Main 方法中执行此操作:

static void Main()
{
  if(!MyApp.IsAlreadyRunning())
  {
    while(true)
    {
      RockAndRollAllNightAndPartyEveryDay();
    }
  }
}

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    为此目的使用互斥锁的正确方法:

    private static Mutex mutex;
    
    static void Main()
    {
        // STEP 1: Create and/or check mutex existence in a race-free way
        bool created;
        mutex = new Mutex(false, "YourAppName-{add-your-random-chars}", out created);
        if (!created)
        {
            MessageBox.Show("Another instance of this application is already running");
            return;
        }
    
        // STEP 2: Run whatever the app needs to do
        Application.Run(new Form1());
    
        // No need to release the mutex because it was never acquired
    }
    

    以上方法不适用于检测同一台机器上的多个用户是否在不同的用户帐户下运行应用程序。类似的情况是一个进程可以在服务主机 下独立运行 both。要使这些工作,请按如下方式创建互斥锁:

            var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
            var mutexsecurity = new MutexSecurity();
            mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.FullControl, AccessControlType.Allow));
            mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.ChangePermissions, AccessControlType.Deny));
            mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.Delete, AccessControlType.Deny));
            _mutex = new Mutex(false, "Global\\YourAppName-{add-your-random-chars}", out created, mutexsecurity);
    

    这里有两个区别 - 首先,需要使用允许其他用户帐户打开/获取它的安全权限来创建互斥锁。其次,名称​​必须在服务主机下运行服务的情况下以“全局”为前缀(不确定其他用户在同一台​​机器上本地运行)。

    【讨论】:

    • 从原始问题中不清楚“上一个实例”是指在这个会话中,对于这个用户还是在这台机器上。 Mutex 名称是每个会话的默认名称,因此每台机器的解决方案应使用类似 @"Global\YourAppName-{add-your-random-chars}" msdn.microsoft.com/en-us/library/aa382954%28VS.85%29.aspx 的名称
    • stackoverflow.com/questions/2415984/… - 我的问题是讨论是否适合简单地检查互斥锁的存在而不获取它。
    • @romkyns:您能解释一下为什么创建“全局\”互斥锁的结果取决于应用程序运行的会话吗?场景:1。用户 A 调用 new Mutex(true, "Global\xxx", out created)。 created 在返回 2 时为 TRUE。 SAME 会话中的用户 B 调用 new Mutex(true, "Global\xxx", out created)。调用成功,返回 3created 为 FALSE。不同会话中的用户 B(切换用户)调用 new Mutex(true, "Global\xxx", out created)。结果是 Exception 'Access to the path "Global\xxx" is denied' 为什么 (2) 的结果与 (3) 不同?
    • @mistika 我想这是为了让您可以创建其他用户无法使用的互斥锁。如果您不完全信任这些用户,这可能会很有用。
    • @romkyns:还是不明白。假设用户 A 不信任用户 B。为什么用户 B 在用户 A 运行的同一会话中调用 CreateMutex 时不会获得 AccessDenied?我看不出为什么用户 A 应该信任用户 B 的进程,因为它与用户 A 的进程在同一会话中运行?
    【解决方案2】:

    Jeroen 已经回答了这个问题,但到目前为止最好的方法是使用互斥锁......而不是通过进程。这是一个更完整的代码答案。

    在看到一些关于竞态条件的 cmets 后,我更新了这个答案,通过使用 Mutex Constructor 来解决这个问题

    Boolean createdNew;
    Mutex mutex;
    
    try
    {      
       mutex = new Mutex(false, "SINGLEINSTANCE" out createdNew);
       if (createdNew == false)
       {
          Console.WriteLine("Error : Only 1 instance of this application can run at a time");
          Application.Exit();
       }
    
       // Run your application
    }
    catch (Exception e)
    {
        // Unable to open the mutex for various reasons
    }
    finally 
    {
        // If this instance created the mutex, ensure that
        // it's cleaned up, otherwise we can't restart the
        // application
        if (mutex && createdNew) 
        {
            mutex.ReleaseMutex();
            mutex.Dispose();
        }
    }
    

    注意try{} finally{} 块。如果您的应用程序崩溃或干净退出,但您没有释放 Mutex,那么您以后可能无法再次重新启动它。

    【讨论】:

    • 我的理解是,在基于 NT 的操作系统中,在进程因任何原因终止后,该进程创建的所有内核对象都由操作系统释放。对吗?
    • 你可以很容易地测试它......刚刚找到这个。 stackoverflow.com/questions/646480/…
    • -1:获取互斥锁存在竞争条件;请参阅我的替代答案。另外,必须提到“SINGLEINSTANCE”应该是应用程序独有的!
    • @Ian - 我的建议太长,无法在评论中发布,这就是我发布答案的原因。当然,您不能跨进程使用“锁定”,但您可以获取互斥体并一次性检查它的存在,没有竞争条件。
    • -1 这不应该是公认的答案,无论是在书中还是其他地方。它可能导致危险的竞争条件,即您的应用程序的多个实例最终成功打开同一个互斥锁,这违背了全局互斥锁的全部目的。
    【解决方案3】:

    最简单(和可靠)的方法是使用互斥锁。使用 Mutex 类的 WaitOne 方法等待互斥锁可用。一个额外的优势,这不需要任何无限循环

    【讨论】:

      【解决方案4】:

      您可以搜索现有系统进程的进程名称。例如代码,see this blog post.

      您还可以使用命名系统 Mutex 来查看您的进程是否已经在运行。
      Here is some sample code. 根据我的经验,这往往更可靠,并且代码更简单、更易于理解。

      【讨论】:

        【解决方案5】:

        这篇文章谈论它:Prevent a second process instance from running。它在 VB.net 中,但您可以转换它。

        编写一个检查当前应用程序是否已经在运行的通用函数的问题来自于这样一个事实,即 Process 对象的 ProcessName 属性似乎被限制为 15 个字符,因此更长的进程名称被截断。
        更安全的方法 检索进程名称是获取其主模块的文件名并删除扩展名。以下可重用例程使用此方法:
        
        Function AppIsAlreadyRunning() As Boolean
            ' get the filename of the main module
            Dim moduleName As String = Process.GetCurrentProcess.MainModule.ModuleName
        
            ' discard the extension to get the process name
            Dim procName As String = System.IO.Path.GetFileNameWithoutExtension(moduleName)
        
            ' return true if there are 2 or more processes with that name
            If Process.GetProcessesByName(procName).Length > 1 Then
                Return True
            End If
        End Function
        

        【讨论】:

          【解决方案6】:
              // Allow running single instance
              string processName = Process.GetCurrentProcess().ProcessName;
              Process[] instances = Process.GetProcessesByName(processName);
          
              if (instances.Length > 1)
              {
                  MessageBox.Show("Application already Running", "Error 1001 - Application Running");
                  return;
              }
          

          如果应用程序已经在运行,则优雅地退出带有消息框的应用程序,如上所示

          【讨论】:

            【解决方案7】:

            您可以在System.Diagnostics 命名空间中使用Process.GetProcessesByName("MyProcessName"); 来检查您的进程的实例是否正在运行。

            编辑:在 cmets 中的观察非常好!这是一种(非常)简单的方法,当然不会涵盖所有基础。

            【讨论】:

            • 来自我评论中的文章... 编写一个检查当前应用程序是否已经在运行的通用函数的问题来自于 Process 对象的 ProcessName 属性似乎仅限于15 个字符,因此较长的进程名称会被截断。例如,创建默认的 ConsoleApplication1 应用程序并运行以下代码:
            • 这也会返回当前进程,因此请务必从该列表中过滤掉当前进程的 ID(或检查以确保返回数组的长度是 >1,而不是 >0)跨度>
            • 如果存在两个应用程序副本并且其中一个已被重命名,则此方法不起作用。
            【解决方案8】:

            使用内核对象是在 Windows 中实现单实例保护的唯一正确方法。

            此声明:

            mutex = Mutex.OpenExisting("SINGLEINSTANCE");

            如果其他人从 Stackoverflow 复制这一行并在您的程序之前运行他们的程序,则将无法工作,因为其他人在您之前抓住了“SINGLEINSTANCE”。您想在互斥体名称中包含 GUID:

            mutex = Mutex.OpenExisting("MyApp{AD52DAF0-C3CF-4cc7-9EDD-03812F82557E}");

            此技术将阻止当前用户运行您的程序的多个实例,但不会阻止其他用户这样做。

            为确保您的应用程序只有一个实例可以在本地计算机上运行,​​您需要这样做:

            mutex = Mutex.OpenExisting("Global\MyApp{AD52DAF0-C3CF-4cc7-9EDD-03812F82557E}");

            查看 CreateMutex api 的帮助。

            【讨论】:

              【解决方案9】:

              在我的一个项目中,我使用了SingleInstance Component

              【讨论】:

                【解决方案10】:

                另一种方法是绑定到本地机器上的地址(就像 TCP 侦听器一样)。一次只有一个进程可以绑定到端口/地址组合。因此,在环回适配器上选择一个端口并使用它。

                这有很好的副作用:

                • 即使有人重命名可执行文件也能正常工作
                • 应用程序崩溃时自行重置
                • 该技术可跨其他操作系统移植

                不利的一面是,如果有另一个应用程序绑定到该特定端口,它可能会失败。


                根据要求,下面是一些绑定到地址/端口的代码。这是从别的东西上扯下来的。它不完整,但必要的部分在这里。

                using System.Net;
                using System.Net.Sockets;
                
                [...]
                
                // Make up a number that's currently unused by you, or any 
                // well-known service. i.e. 80 for http, 22 for ssh, etc..
                int portNum = 2001;  
                
                // This binds to any (meaning all) adapters on this system 
                IPAddress ipAddress = IPAddress.Any;
                IPEndPoint localEndPoint = new IPEndPoint(ipAddress, portNum);
                Socket listener = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp );
                
                // The next statement will throw an exception if anyone has done this Bind!
                listener.Bind(localEndPoint);
                

                只要侦听器没有被垃圾回收(超出范围)或程序没有终止:该适配器上的端口是您的,并且只有您的。如果listener 发生任何事情,那么它就可以供其他人使用。出于锁定的目的,您可能应该让 listener 在某处保持静态。

                【讨论】:

                • 你有关于如何做到这一点的示例代码吗?另外,“应用程序崩溃时自行重置”是什么意思?
                • 这个技术相当脆弱,互斥技术要好得多。
                • 如果我安装在我机器上的应用程序无法运行只是因为我将 VNC 配置为侦听该端口(我出于各种原因这样做),那将是非常烦人的。我怀疑这也可能触发防火墙提示。请改用互斥锁方法。
                猜你喜欢
                • 2011-05-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-08-16
                • 1970-01-01
                • 2020-05-03
                • 1970-01-01
                相关资源
                最近更新 更多