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

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

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.");
}

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

10 ответов


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

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

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

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


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

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

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

для этой цели более привычно и удобно использовать события 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\ префиксы для имени события (или мьютекса). Если ваше приложение предназначено для каждого пользователя, просто добавьте к имени события имя пользователя, вошедшего в систему.


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

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

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

используя мьютекс, вы в основном используете define number 4.


Я использую этот метод, я считаю его безопасным, потому что мьютекс уничтожается, если он долго не удерживается каким-либо приложением (и приложения прекращаются, если они не могут изначально создать мьютекс). Это может работать или не работать одинаково в " 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 -- поставляется с Джоном скитом ; -)


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

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

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

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

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


Да, это безопасно, я бы предложил следующий шаблон, потому что вам нужно убедиться, что 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());                  
    }
}

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

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

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

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

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

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

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

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


используйте приложение с таймаутом и параметрами безопасности, избегая исключения 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");
        }
    }

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

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

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, и если оно получает это сообщение, оно представляет себя пользователю, форме.Показать() или что-то вроде