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

StackOverflow https://stackoverflow.com/questions/646480

Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

<Ч>

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

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

<Ол>
  • Нормальный запуск процесса.
  • Второй процесс запускается и получает дескриптор мьютекса, но отключается перед вызовом WaitOne .
  • Процесс № 1 внезапно завершен. Мьютекс не уничтожен, потому что процесс № 2 имеет дескриптор. Вместо этого он устанавливается в заброшенное состояние.
  • Второй процесс запускается снова и получает 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 \ для имени события (или мьютекса). Если ваше приложение для каждого пользователя, просто добавьте к имени события подходящее искаженное имя вошедшего в систему пользователя.

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

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

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

    Я использую этот метод, я считаю, что это безопасно, потому что 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 - поставляется с Джоном Скитом; -)

    В 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 = <*>quot;Local\\{applicationName}";
            else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
                name = <*>quot;Global\\{applicationName}{Environment.UserDomainName}";
            else
                name = <*>quot;Global\\{applicationName}";
    
            try
            {
                bool created;
                _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
                FirstInstance = created;
            }
            catch
            {
            }
        }
    
        public void Dispose()
        {
            _event.Dispose();
        }
    }
    

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

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

    Альтернативой является PInvoke для 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. Получите System.Diagnostics.Process вашего приложения, используя Process.GetCurrentProcess () 2. Просмотрите коллекцию открытых процессов с текущим именем вашего приложения, используя Process.GetProcessesByName (thisProcess.ProcessName) 3. Проверьте каждый process.Id по сравнению с thisProcess.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, и если оно получает это сообщение, оно представляется пользователю, Form .Show () или еще много чего

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top