【问题标题】:C#: How to prevent two instances of an application from doing the same thing at the same time?C#:如何防止应用程序的两个实例同时做同样的事情?
【发布时间】:2011-01-20 18:09:14
【问题描述】:

如果您在应用程序中有两个线程,并且您不希望它们同时运行某段代码,您可以锁定该段代码,如下所示:

lock (someObject) {
    // ... some code
}

但是你如何在不同的进程中做同样的事情呢?我认为这就是你使用“全局互斥锁”的目的,所以我尝试了Mutex 类以各种方式,但它似乎无法满足我的要求,即:

  • 如果您是唯一的实例,请继续运行代码。
  • 如果您是第二个实例,请等待第一个实例完成,然后运行代码。
  • 不要抛出异常。

我遇到的问题:

  • 只是在using(){...} 子句中实例化Mutex 对象似乎没有任何作用;这两个实例仍然可以愉快地同时运行
  • 在 Mutex 上调用 .WaitOne() 会导致第一个实例运行,第二个实例等待,但第二个实例无限期地等待,即使在第一个调用 .ReleaseMutex() 并离开 using(){} 范围之后也是如此。
  • .WaitOne() 在第一个进程退出时抛出异常 (System.Threading.AbandonedMutexException)。

我该如何解决这个问题?非常欢迎不涉及 Mutex 的解决方案,尤其是因为 Mutex 似乎是特定于 Windows 的。

【问题讨论】:

  • 您正在使用 .NET,并抱怨 Mutex 是特定于 Windows 的?
  • @Randolpho:谁的目标是实现整个 .NET 库。包括System.Threading.Mutex。如果它是 .NET 库的一部分,您可以期望它在任何 .NET 平台上运行(包括单声道,一旦它完成了它的那部分实现)。
  • 哦,我知道了。我只是说'

标签: c# .net mutex mutual-exclusion


【解决方案1】:

我有两个应用程序:

ConsoleApplication1.cs

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");

            Console.WriteLine("ConsoleApplication1 created mutex, waiting . . .");

            mutex.WaitOne();

            Console.Write("Waiting for input. . .");
            Console.ReadKey(true);

            mutex.ReleaseMutex();
            Console.WriteLine("Disposed mutex");
        }
    }
}

ConsoleApplication2.cs

using System;
using System.Threading;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = new Mutex(false, "AwesomeMutex");
            Console.WriteLine("ConsoleApplication2 Created mutex");

            mutex.WaitOne();

            Console.WriteLine("ConsoleApplication2 got signalled");

            mutex.ReleaseMutex();
        }
    }
}

启动 ConsoleApplication1,然后是 ConsoleAplication2,运行良好,没有错误。如果您的代码仍然失败,那是您的代码存在错误,而不是 Mutex 类。

【讨论】:

  • 非常感谢。我可以发誓这正是我所做的,但我一定做了一些不同的事情,因为它不起作用,现在它起作用了。再次感谢。
【解决方案2】:

【讨论】:

  • 是的,一个命名的 Mutex 是进程可以共享这个窗口原语的方式。
  • 我做到了。它没有用。一些工作示例代码会很好。
  • @Timwi - 您能否提供您正在使用但不起作用的代码?您可能正在执行其他操作导致此操作失败。
  • 我怀疑@Timwi 可能会尝试多次获取互斥锁,但只释放一次。
【解决方案3】:

我已经成功地将 Mutex 用于此目的,并且可以确认它可以工作,尽管有一些怪癖。

我家里有一个完整的工作代码示例。如果您希望我今晚添加代码示例,只需对此答案发表评论即可。

更新:

这是我的生产应用程序中的精简代码。这是一个控制台应用程序,但相同的主体应适用于任何类型的应用程序。使用

的命令行参数运行
--mutex 

测试互斥逻辑。

请注意,在我的情况下,互斥锁确实保护了大部分进程,但没有理由必须以这种方式使用它。这正是本案例所需要的。

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace MyNameSpace
{
    class Program
    {
        // APP_GUID can be any unique string.  I just opted for a Guid.  This isn't my real one :-)
        const string APP_GUID = "1F5D24FA-7032-4A94-DA9B-F2B6240F45AC";

        static int Main(string[] args)
        {
            bool testMutex = false;
            if (args.Length > 0 && args[0].ToUpper() == "--MUTEX")
            {
                testMutex = true;
            }

            // Got variables, now only allow one to run at a time.

            int pid = System.Diagnostics.Process.GetCurrentProcess().Id;

            int rc = 0;

            Mutex mutex = null;
            bool obtainedMutex = false;
            int attempts = 0;
            int MAX_ATTEMPTS = 4;

            try
            {
                mutex = new Mutex(false, "Global\\" + APP_GUID);

                Console.WriteLine("PID " + pid + " request mutex.");

                while (!obtainedMutex && attempts < MAX_ATTEMPTS)
                {
                    try
                    {
                        if (!mutex.WaitOne(2000, false))
                        {
                            Console.WriteLine("PID " + pid + " could not obtain mutex.");
                            // Wait up to 2 seconds to get the mutex
                        }
                        else
                        {
                            obtainedMutex = true;
                        }
                    }
                    catch (AbandonedMutexException)
                    {
                        Console.WriteLine("PID " + pid + " mutex abandoned!");
                        mutex = new Mutex(false, "Global\\" + APP_GUID); // Try to re-create as owner
                    }

                    attempts++;
                }

                if (!obtainedMutex)
                {
                    Console.WriteLine("PID " + pid + " gave up on mutex.");
                    return 102;
                }


                Console.WriteLine("PID " + pid + " got mutex.");

                // This is just to test the mutex... keep one instance open until a key is pressed while
                // other instances attempt to acquire the mutex
                if (testMutex)
                {
                    Console.Write("ENTER to exit mutex test....");
                    Console.ReadKey();
                    return 103;
                }

                // Do useful work here

            }
            finally
            {
                if (mutex != null && obtainedMutex) mutex.ReleaseMutex();
                mutex.Close();
                mutex = null;
            }

            return rc;
        }
    }
}

【讨论】:

  • @Timwi:今晚我会发布代码。为了让它工作,我确实挠了挠头,但现在它被用于大批量生产应用程序中,没有任何问题。
  • 虽然 finally 可能会崩溃..如果互斥锁为空,则关闭将不起作用..我不确定它怎么可能是空的,但如果它不能,那么测试空不需要。
【解决方案4】:

虽然我建议您使用 Mutex 并调查问题是否出在您的代码本身,但一种非常复杂且高度脆弱的替代方案可能是使用“锁定文件”。

基本上,您在文件系统中创建一个临时文件,其名称可以被两个进程识别。如果文件存在,则锁定到位;其他进程应该释放任何其他锁,等待并尝试重新获取。如果文件不存在,则锁未到位;进程应该创建文件以完成锁定并继续处理,释放锁定时删除文件。

正如我所说,它非常复杂和脆弱,但它不使用 Mutex。

或者,使用互斥锁。

【讨论】:

    【解决方案5】:

    如果您不想使用互斥体,我会说使用存在的文件来滚动您自己的文件将是一个简单的解决方案。如果文件存在,则不要开始一个新文件。但是,您必须处理进程提前终止且文件未清理的异常情况。

    暂时回到互斥锁,这只是一个操作系统“对象”,需要一个更好的术语。在您使用的每个操作系统上,您确实需要这样的东西。我确信其他 .net clr 也将允许您访问这些系统原语。我只是使用在每个平台上可能不同的已定义程序集来结束每个程序。

    希望这是有道理的。

    【讨论】:

      【解决方案6】:

      也许我把问题简单化了,但我过去只是检查过进程名称。

      你可以只使用这个函数等到另一个完成,然后运行当前进程。

      ''' <summary>
      ''' Is this app already running as a standalone exe?
      ''' </summary>
      ''' <returns></returns>
      ''' <remarks>
      ''' NOTE: The .vshost executable runs the whole time Visual Studio is open, not just when debugging.
      ''' So for now we're only checking if it's running as a standalone.
      ''' </remarks>
      public bool AlreadyRunning()
      {
          const string VS_PROCESS_SUFFIX = ".vshost";
      
          //remove .vshost if present
          string processName = Process.GetCurrentProcess.ProcessName.Replace(VS_PROCESS_SUFFIX, string.Empty);
          
          int standaloneInstances = Process.GetProcessesByName(processName).Length;
          //Dim vsInstances As Integer = Process.GetProcessesByName(processName & VS_PROCESS_SUFFIX).Length
          
          //Return vsInstances + standaloneInstances > 1
          return standaloneInstances > 1;
      }
      

      【讨论】:

      • 您的回答要求我等待整个过程完成,这与我的问题无关。
      猜你喜欢
      • 1970-01-01
      • 2017-08-31
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 2010-10-08
      • 1970-01-01
      相关资源
      最近更新 更多