如何防止两个应用程序同时执行相同的任务?

9
如果您的应用程序中有两个线程,而且您不希望它们同时运行某个特定的代码段,您可以在代码周围放置一个锁,像这样:
lock (someObject) {
    // ... some code
}

但是如何在不同的进程之间做相同的事情呢?我认为这就是使用“全局互斥锁”的地方,所以我尝试了各种方式来使用Mutex类,但它似乎不能满足我的要求,这些要求是:
  • 如果你是唯一的实例,请继续运行代码。
  • 如果你是第二个实例,请等待第一个实例完成,然后运行代码。
  • 不要抛出异常。
我遇到的问题有:
  • 只需在using(){...}子句中实例化Mutex对象似乎什么也没发生;两个实例仍然可以同时运行。
  • 对Mutex调用.WaitOne()会导致第一个实例运行,第二个实例等待,但第二个实例会无限期地等待,即使第一个实例调用了.ReleaseMutex()并离开了using(){}范围。
  • .WaitOne()在第一个进程退出时抛出异常(System.Threading.AbandonedMutexException)。
如何解决这个问题?不涉及Mutex的解决方案非常受欢迎,特别是因为Mutex似乎是Windows特定的。

3
您正在使用.NET,并抱怨Mutex是仅适用于Windows的吗? - Anon.
3
@Anon:http://www.mono-project.com/Main_Page - Randolpho
1
@Randolpho: 谁的目标是实现整个 .NET 库,包括 System.Threading.Mutex。如果它是 .NET 库的一部分,你可以期望它在任何 .NET 平台上运行(包括 mono,一旦它完成了该部分的实现)。 - Anon.
哦,我知道。我只是说了一下。 - Randolpho
7个回答

6

I have two applications:

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 可以完美地运行且无错误。如果你的代码仍然崩溃,则是你的代码有 bug,而不是 Mutex 类的问题。


非常感谢。我本以为这恰好就是我所做的,但我肯定做了些不同的事情,因为它没有起作用,现在它可以了。再次感谢。 - Timwi

4

我已经成功地使用了互斥锁来实现这个目的,并且可以确认它有效,尽管有一些小问题。

我在家中有一个完整的工作代码示例。如果您想在今晚添加代码示例,请在此答案下发表评论。

更新:

这是我生产应用程序中简化的代码。这是一个控制台应用程序,但同样的原则适用于任何类型的应用程序。使用命令行参数运行

--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:我今晚会发布代码。我曾经为此苦思冥想了一段时间,但现在它已被用于高流量的生产应用程序中,没有任何问题。 - Eric J.
最后一步可能会出问题,如果互斥量为空,关闭操作将无法正常运作。我不确定互斥量如何为空,但如果它确实为空,则无需进行空值检测。 - baash05

4

是的,命名互斥锁是进程之间共享这个Windows原语的方式。 - Preet Sangha
我试过了,但是没有成功。能否提供一些可行的示例代码? - Timwi
1
@Timwi - 你能提供一下你正在使用但无法正常工作的代码吗?可能是你还做了其他导致它失败的事情。 - Nick
1
我怀疑 @Timwi 可能尝试多次获取互斥锁,但只释放了一次。 - Anon.

0

我不得不在表单应用程序中这样做,简单而有效。

static void Main()
{ 

var processes = Process.GetProcesses().ToList();
            int count = 0;
            foreach (var item in processes)
            {
                if (item.ProcessName == "NameOfYourProcess")
                {
                    count++;
                }
                if(count > 1) 
                {
                    Environment.Exit(0);
                }
            }
}

0

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

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

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

或者,使用Mutex。


0

如果您不想使用Mutex,我建议您使用一个已存在的文件来自己实现。如果文件已经存在,则不要启动新的文件。但是,您必须处理异常情况,例如进程提前终止而文件没有被清理。

回到Mutex,这只是一个操作系统的“对象”,为了更好的术语。您确实需要在每个操作系统上都有类似的东西。我相信其他 .net clr 也允许您访问这些系统原语。我只是将每个封装在一个定义的程序集中,这些程序集在每个平台上可能不同。

希望这有意义。


-1
也许我过于简化这个问题了,但我刚刚检查了过去的进程名称。
您可以使用此函数等待其他进程完成,然后运行当前进程。
''' <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;
}

你的回答要求我等待整个过程完成,这使得它与我的问题无关。 - Timwi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接