Является ли использование Mutex безопасным для нескольких экземпляров одной и той же программы? - PullRequest
44 голосов
/ 14 марта 2009

Я использую этот код для предотвращения одновременного запуска второго экземпляра моей программы, это безопасно?

Mutex appSingleton = new System.Threading.Mutex(false, "MyAppSingleInstnceMutx");
if (appSingleton.WaitOne(0, false)) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
    appSingleton.Close();
} else {
    MessageBox.Show("Sorry, only one instance of MyApp is allowed.");
}

Я обеспокоен тем, что если что-то выдает исключение и приложение падает, Mutex все равно будет удерживаться. Это правда?

Ответы [ 10 ]

58 голосов
/ 14 марта 2009

Для этого более привычно и удобно использовать события Windows. Э.Г.

static EventWaitHandle s_event ;

bool created ;
s_event = new EventWaitHandle (false, 
    EventResetMode.ManualReset, "my program#startup", out created) ;
if (created) Launch () ;
else         Exit   () ;

Когда ваш процесс завершается или завершается, Windows закроет событие для вас и уничтожит его, если не останется открытых дескрипторов.

Добавлено : для управления сеансами используйте префиксы Local\ и Global\ для имени события (или мьютекса). Если ваше приложение для каждого пользователя, просто добавьте подходящее искаженное имя вошедшего в систему пользователя к имени события.

40 голосов
/ 14 марта 2009

В общем, да, это будет работать. Однако дьявол кроется в деталях.

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

К сожалению, даже с блоком finally вы должны иметь дело с возможностью того, что процесс будет завершен без освобождения мьютекса. Это может произойти, например, если пользователь убивает процесс через TaskManager. В вашем коде есть условие гонки, которое позволит второму процессу получить AbandonedMutexException в вызове WaitOne. Вам понадобится стратегия восстановления для этого.

Я рекомендую вам прочитать информацию о классе Mutex . Использовать его не всегда просто.


Расширение возможностей гонки:

Может произойти следующая последовательность событий, которая может вызвать выброс второго экземпляра приложения:

  1. Обычный запуск процесса.
  2. Второй процесс запускается и вызывает дескриптор мьютекса, но отключается до вызова WaitOne.
  3. Процесс # 1 внезапно завершен. Мьютекс не уничтожен, потому что процесс № 2 имеет дескриптор. Вместо этого он устанавливается в заброшенное состояние.
  4. Второй процесс запускается снова и получает AbanonedMutexException.
9 голосов
/ 13 апреля 2010

Я использую этот метод, я считаю, что это безопасно, потому что Mutex уничтожается, если долго не удерживается каким-либо приложением (и приложения закрываются, если они не могут изначально создать Mutext). Это может работать или не работать одинаково в «AppDomain-процессы» (см. Ссылку внизу):

// Make sure that appMutex has the lifetime of the code to guard --
// you must keep it from being collected (and the finalizer called, which
// will release the mutex, which is not good here!).
// You can also poke the mutex later.
Mutex appMutex;

// In some startup/initialization code
bool createdNew;
appMutex = new Mutex(true, "mutexname", out createdNew);
if (!createdNew) {
  // The mutex already existed - exit application.
  // Windows will release the resources for the process and the
  // mutex will go away when no process has it open.
  // Processes are much more cleaned-up after than threads :)
} else {
  // win \o/
}

Вышесказанное страдает от замечаний в других ответах / комментариях о том, что вредоносные программы могут сидеть на мьютексе. Не беспокойся здесь. Также нефиксированный мьютекс создан в «Локальном» пространстве. Это, наверное, правильно здесь.

См .: http://ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx - поставляется с Джоном Скитом; -)

9 голосов
/ 14 марта 2009

Вы можете использовать мьютекс, но сначала убедитесь, что это действительно то, что вы хотите.

Потому что «избегание нескольких экземпляров» не имеет четкого определения. Это может означать

  1. Предотвращение запуска нескольких экземпляров в одном и том же сеансе пользователя, независимо от количества рабочих столов в этом сеансе пользователя, но одновременное выполнение нескольких экземпляров для разных сеансов пользователя.
  2. Избегание нескольких экземпляров, запущенных на одном рабочем столе, но позволяющих запускать несколько экземпляров, пока каждый находится на отдельном рабочем столе.
  3. Предотвращение запуска нескольких экземпляров для одной и той же учетной записи пользователя, независимо от того, сколько рабочих столов или сеансов выполняется под этой учетной записью, но одновременное выполнение нескольких экземпляров для сеансов, запущенных под другой учетной записью пользователя.
  4. Избегание нескольких экземпляров, запущенных на одном компьютере. Это означает, что независимо от того, сколько рабочих столов используется произвольным числом пользователей, может быть запущен не более одного экземпляра программы.

Используя мьютекс, вы в основном используете определение числа 4.

3 голосов
/ 14 марта 2009

В Windows завершение процесса приводит к следующим результатам:

  • Все остальные потоки в процессе помечены для завершения.
  • Все ресурсы, выделенные процессом, освобождаются.
  • Все объекты ядра закрыты.
  • Код процесса удален из памяти.
  • Код завершения процесса установлен.
  • Объект процесса сигнализируется.

Объекты Mutex являются объектами ядра, поэтому все удерживаемые процессом закрываются по завершении процесса (в любом случае, в Windows).

Но обратите внимание на следующий бит из документации CreateMutex ():

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

2 голосов
/ 13 июля 2016

Вот фрагмент кода

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
2 голосов
/ 14 марта 2009

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

using( Mutex mutex = new Mutex( false, "mutex name" ) )
{
    if( !mutex.WaitOne( 0, true ) )
    {
        MessageBox.Show("Unable to run multiple instances of this program.",
                        "Error",  
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }
    else
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());                  
    }
}
0 голосов
/ 14 декабря 2015

Вот как я подошел к этому

В классе Программы: 1. Получите System.Diagnostics.Process вашего приложения, используя Process.GetCurrentProcess () 2. Просмотрите коллекцию открытых процессов с текущим именем вашего приложения, используя Process.GetProcessesByName (thisProcess.ProcessName) 3. Проверьте каждый процесс. Идентификатор против thisProcess.Id, и если экземпляр уже открыт, то как минимум 1 будет соответствовать имени, но не идентификатору, в противном случае продолжайте открывать экземпляр

using System.Diagnostics;

.....    

static void Main()
{
   Process thisProcess = Process.GetCurrentProcess();
   foreach(Process p in Process.GetProcessesByName(thisProcess.ProcessName))
   {
      if(p.Id != thisProcess.Id)
      {
         // Do whatever u want here to alert user to multiple instance
         return;
      }
   }
   // Continue on with opening application

Приятным завершением этой работы было бы представление пользователю уже открытого экземпляра, скорее всего, они не знали, что он был открыт, поэтому давайте покажем им, что это было. Для этого я использую User32.dll для трансляции сообщения в цикл обмена сообщениями Windows, пользовательское сообщение, и мое приложение прослушивает его в методе WndProc, и если оно получает это сообщение, оно представляется пользователю, Form .Show () или еще много чего

0 голосов
/ 28 июля 2015

Используйте приложение с настройками тайм-аута и безопасности, избегая AbandonedMutexException. Я использовал свой пользовательский класс:

private class SingleAppMutexControl : IDisposable
    {
        private readonly Mutex _mutex;
        private readonly bool _hasHandle;

        public SingleAppMutexControl(string appGuid, int waitmillisecondsTimeout = 5000)
        {
            bool createdNew;
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex = new Mutex(false, "Global\\" + appGuid, out createdNew, securitySettings);
            _hasHandle = false;
            try
            {
                _hasHandle = _mutex.WaitOne(waitmillisecondsTimeout, false);
                if (_hasHandle == false)
                    throw new System.TimeoutException();
            }
            catch (AbandonedMutexException)
            {
                _hasHandle = true;
            }
        }

        public void Dispose()
        {
            if (_mutex != null)
            {
                if (_hasHandle)
                    _mutex.ReleaseMutex();
                _mutex.Dispose();
            }
        }
    }

и используйте его:

    private static void Main(string[] args)
    {
        try
        {
            const string appguid = "{xxxxxxxx-xxxxxxxx}";
            using (new SingleAppMutexControl(appguid))
            {
                //run main app
                Console.ReadLine();
            }
        }
        catch (System.TimeoutException)
        {
            Log.Warn("Application already runned");
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Fatal Error on running");
        }
    }
0 голосов
/ 14 марта 2009

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

Одно предостережение: мьютексный подход не позволяет вам активировать первый экземпляр приложения, когда пользователь пытается запустить второй экземпляр.

Альтернативой является PInvoke для FindWindow, за которым следует SetForegroundWindow в первом случае. Другой вариант - проверить ваш процесс по имени:

Process[] processes = Process.GetProcessesByName("MyApp");
if (processes.Length != 1)
{
    return;
} 

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

Другая проблема с этими двумя последними альтернативами заключается в том, что они не будут работать при использовании служб терминалов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...