.net 如何防止应用程序的两个示例同时执行相同的操作?

vojdkbi0  于 2022-12-20  发布在  .NET
关注(0)|答案(7)|浏览(126)

如果应用程序中有两个线程,并且您不希望它们同时运行某段代码,则只需在这段代码周围加一个锁,如下所示:

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

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

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

我遇到的问题:

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

不涉及Mutex的解决方案是非常受欢迎的,特别是因为Mutex似乎是特定于Windows的。

pdtvr36n

pdtvr36n1#

我有两个应用程序:
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类的错误。

nzk0hqpo

nzk0hqpo3#

我已经成功地将互斥锁用于这个目的,并且可以确认它是有效的,尽管有一些怪癖。
我有一个完整的工作代码示例在家里。只是张贴评论这个答案,如果你想让我添加代码示例今天晚上。
更新:
这是我的生产应用程序的精简代码。这是一个控制台应用程序,但相同的原则应适用于任何类型的应用程序。使用命令行参数运行

--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;
        }
    }
}
c9x0cxw0

c9x0cxw04#

尽管我建议您使用互斥锁并调查问题是否出在代码本身,但一个非常复杂且非常脆弱的替代方法可能是使用“锁定文件”。
基本上,您在文件系统中创建一个临时文件,文件名为两个进程都能识别的名称。如果文件存在,则锁就到位了;另一个进程应该释放任何其他锁,等待,并尝试重新获取。如果文件不存在,则锁不存在;该进程应当创建文件以完成锁定并继续处理,当释放锁定时删除该文件。
正如我所说,它非常复杂和脆弱,但它不使用互斥锁。
或者,使用互斥锁。

6ss1mwsb

6ss1mwsb5#

如果你不想使用互斥锁,我会说使用一个已经存在的文件来滚动你自己的互斥锁会是一个简单的解决方案。如果文件已经存在,就不要启动一个新的互斥锁。但是,你必须处理进程提前终止和文件没有清理的异常情况。
回到互斥锁,这只是一个操作系统的“对象”,因为没有一个更好的术语。你真的需要在你使用的每个操作系统上都有这样的东西。我相信其他的.net clr也会允许你访问那些系统原语。我只是用一个定义好的程序集来 Package 它们,这个程序集在每个平台上都可能是不同的。
希望这是有道理的。

kgqe7b3p

kgqe7b3p6#

我不得不在一个表单应用程序中完成这一操作,简单但工作正常。

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

wh6knrhe7#

也许我过于简化了这个问题,但是我以前只是检查了进程名称。
您可以使用此函数等待其他进程完成,然后运行当前进程。

''' <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;
}

相关问题