Подтвердить что ты не робот

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

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

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 все равно будет удерживаться. Это правда?

4b9b3361

Ответ 1

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

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

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

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


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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 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 - поставляется с Jon Skeet; -)

Ответ 5

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

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

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

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

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

Ответ 6

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

Ответ 7

Здесь фрагмент кода

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

Ответ 10

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

В классе программы: 1. Получить System.Diagnostics.Process вашего приложения с помощью Process.GetCurrentProcess() 2. Пройдите через коллекцию открытых процессов с текущим именем вашего приложения, используя Process.GetProcessesByName(thisProcess.ProcessName) 3. Проверьте каждый процесс.Ид против 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() или whatnot