C #: Как предотвратить одновременное выполнение двумя экземплярами приложения одного и того же приложения? - PullRequest
7 голосов
/ 25 февраля 2010

Если у вас есть два потока в приложении, и вы не хотите, чтобы они запускали определенный фрагмент кода одновременно, вы можете просто заблокировать этот фрагмент кода, например:

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

Но как вы делаете то же самое в разных процессах? Я думал, что это то, для чего вы используете «глобальный мьютекс», поэтому я пробовал класс Mutex различными способами, но, похоже, он не соответствует моим требованиям:

  • Если вы единственный экземпляр, запустите код.
  • Если вы второй экземпляр, дождитесь завершения первого, затем запустите код.
  • Не бросайте исключения.

Проблемы, с которыми я столкнулся:

  • Просто создание экземпляра объекта Mutex в предложении using(){...}, похоже, ничего не делает; два экземпляра по-прежнему счастливо работают одновременно
  • Вызов .WaitOne() на Mutex заставляет первый экземпляр работать, а второй ждать, но второй ждет бесконечно, даже после первых вызовов .ReleaseMutex() и покидает область действия using(){}.
  • .WaitOne() выдает исключение при выходе из первого процесса (System.Threading.AbandonedMutexException).

Как мне это решить? Решения, не включающие Mutex, приветствуются, особенно если учесть, что Mutex, похоже, специфичен для Windows.

Ответы [ 6 ]

5 голосов
/ 25 февраля 2010

У меня есть два приложения:

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.

4 голосов
/ 25 февраля 2010
4 голосов
/ 25 февраля 2010

Я успешно использовал Mutex именно для этой цели и могу подтвердить, что он работает, хотя было несколько причуд.

У меня есть полный рабочий пример кода дома. Просто оставьте комментарий к этому ответу, если вы хотите, чтобы я добавил пример кода этим вечером.

UPDATE:

Вот урезанный код из моего производственного приложения. Это консольное приложение, но тот же принципал должен применяться к любому типу приложений. Запустить с аргументом командной строки

--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;
        }
    }
}
0 голосов
/ 25 февраля 2010

Возможно, я слишком упрощаю проблему, но я только что проверил имя процесса в прошлом.

Вы можете просто использовать эту функцию, чтобы дождаться выполнения другой функции, а затем запустить текущий процесс.

''' <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;
}
0 голосов
/ 25 февраля 2010

Если вы не хотите использовать Mutex, я бы сказал, что переходить на собственное использование, скажем, существующего файла, было бы простым решением. Если файл существует, не начинайте новый. Однако вам придется обрабатывать исключительные случаи, когда процесс завершается досрочно и файл не очищается.

Возвращаясь к мьютексу на мгновение, это просто «объект» ОС из-за отсутствия лучшего термина. Вам действительно нужно такое на каждой ОС, которую вы используете. Я уверен, что другой .net clr также позволит вам получить доступ к этим системным примитивам. Я бы просто обернул каждую из них, используя определенную сборку, которая может быть разной на каждой платформе.

Надеюсь, это имеет смысл.

0 голосов
/ 25 февраля 2010

Хотя я бы порекомендовал вам использовать Mutex и выяснить, связана ли проблема с самим вашим кодом, одна очень сложная и очень хрупкая альтернатива может заключаться в использовании «блокировки файлов».

По сути, вы создаете временный файл в файловой системе с именем, которое распознают оба процесса. Если файл существует, блокировка на месте; другой процесс должен снять любые другие блокировки, подождать и попытаться повторно получить данные. Если файл не существует, блокировка не на месте; процесс должен создать файл для завершения блокировки и продолжить обработку, удаляя файл при снятии блокировки.

Как я уже сказал, он очень сложный и хрупкий, но не использует Mutex.

Или используйте Mutex.

...