Está usando um Mutex para evitar que várias instâncias do mesmo programa seja executado seguro?

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

Pergunta

Eu estou usando este código para evitar uma segunda instância do meu programa seja executado ao mesmo tempo, é seguro?

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

Eu estou preocupado que se algo gera uma exceção eo aplicativo trava que o Mutex ainda será realizada. Isso é verdade?

Foi útil?

Solução

Em sim gerais que isso vai funcionar. No entanto, o diabo está nos detalhes.

Em primeiro lugar você deseja fechar o mutex em um bloco finally. Caso contrário, seu processo poderia abruptamente terminar e deixá-lo em um estado sinalizado, como uma exceção. Isso faria com que as instâncias de processo futuro não seria capaz de iniciar.

Infelizmente, porém, mesmo com um bloco finally você deve lidar com o potencial que um processo será encerrado sem liberar o mutex. Isso pode acontecer, por exemplo, se um usuário mata o processo através do TaskManager. Existe uma condição de corrida em seu código que permitiria a um segundo processo para obter uma AbandonedMutexException na chamada WaitOne. Você vai precisar de uma estratégia de recuperação para isso.

Encorajo-vos a ler sobre os detalhes do Mutex classe. Usando nem sempre é simples.


Expandindo a possibilidade condição de corrida:

A seguinte seqüência de eventos pode ocorrer o que causaria uma segunda instância do aplicativo para jogar:

  1. inicialização do processo normal.
  2. Em segundo lugar processo inicia e adquire um identificador para o mutex, mas é comutada antes da chamada WaitOne.
  3. Processo nº 1 é abruptamente encerrado. O mutex não é destruído porque o processo # 2 tem uma alça. É em vez definida como um estado de abandono.
  4. O segundo processo começa a correr novamente e recebe um AbanonedMutexException.

Outras dicas

É mais comum e conveniente de usar eventos do Windows para esta finalidade. Por exemplo.

static EventWaitHandle s_event ;

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

Quando suas saídas de processo ou termina, o Windows irá fechar o evento para você, e destruí-lo se não identificadores abertos permanecem.

Adicionado : para gerenciar sessões, uso Local\ e prefixos Global\ para o nome do evento (ou exclusão mútua). registrada no nome do usuário para o nome do evento Se sua aplicação for por usuário, basta adicionar um mutilado adequadamente.

Você pode usar um mutex, mas primeiro certifique-se de que este é realmente o que você quer.

Porque "evitando várias instâncias" não está claramente definida. Pode significar

  1. Evitando várias instâncias iniciados na mesma sessão de usuário, não importa o número de ecrãs que sessão do usuário tem, mas permitindo que várias instâncias para executar simultaneamente para as sessões de utilizador diferente.
  2. Evitando várias instâncias iniciados na mesma área de trabalho, mas permitindo que várias instâncias para executar, desde que cada um está em uma área de trabalho separada.
  3. Evitando várias instâncias iniciados para a mesma conta de usuário, não importa quantos desktops ou sessões que funcionam sob esta conta existe, mas permitindo que várias instâncias para executar simultaneamente para as sessões em execução sob uma conta de usuário diferente.
  4. Evitando várias instâncias iniciados na mesma máquina. Isto significa que não importa quantas desktops são usados ??por um número arbitrário de usuários, pode haver no máximo uma instância do programa em execução.

Ao usar um mutex, você está usando basicamente o número 4 definem.

Eu uso este método, eu acredito que ele seja seguro porque o Mutex é destruído se não muito realizada por qualquer aplicação (e aplicações são terminadas se se eles não podem, inicialmente, criar o Mutext). Isto pode ou não pode funcionar de forma idêntica em 'AppDomain-processos' (veja o link na parte inferior):

// 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/
}

Os sofre acima das notas em outras respostas / comentários sobre programas maliciosos ser capaz de se sentar em um mutex. Não é uma preocupação aqui. Além disso, mutex unprefixed criado no espaço "Local". É provavelmente o direito coisa, aqui.

Veja: http: // ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx - vem com Jon Skeet; -)

No Windows, encerrando um processo tem os seguintes resultados:

  • Qualquer tópicos restantes no processo estão marcadas para rescisão.
  • quaisquer recursos alocados pelo processo são liberados.
  • Todos os objetos do kernel estão fechados.
  • O código de processo é removido da memória.
  • O código de saída do processo é configurado.
  • O objeto do processo é sinalizado.

objetos Mutex são objetos de kernel, portanto, qualquer realizada por um processo são fechados quando o termina de processo (no Windows de qualquer maneira).

Mas, observe o seguinte bit do CreateMutex () docs:

Se você estiver usando um mutex nomeado para limitar a sua aplicação a uma única instância, um usuário mal-intencionado pode criar este mutex antes de fazer e impedir que seu aplicativo seja iniciado.

Sim, é seguro, eu sugiro o seguinte padrão, porque você precisa ter certeza de que o Mutex fica sempre liberado.

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

Aqui está o trecho de código

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

Se você quiser usar uma abordagem baseada mutex, você deve realmente usar um mutex local para restringir a abordagem de apenas sessão de login do usuário curent. Bem como observar o outro ressalva importante nesse link sobre disposição de recursos robusto, com a abordagem de exclusão mútua.

Uma ressalva é que uma abordagem baseada mutex não permite que você para ativar a primeira instância do aplicativo quando um usuário tenta iniciar uma segunda instância.

Uma alternativa é PInvoke para FindWindow seguido por SetForegroundWindow no primeiro exemplo. Outra alternativa é para verificar se o seu processo pelo nome:

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

Ambas as últimas alternativas têm uma condição de corrida hipotética em que duas instâncias do aplicativo pode ser iniciado simultaneamente e, em seguida, detectar o outro. Este é improvável que isso aconteça na prática - de fato, durante os testes que eu não poderia fazer isso acontecer.

Outro problema com estas duas últimas alternativas é que eles não vão trabalhar com os Serviços de terminal.

Use aplicativo com configurações de tempo limite e de segurança com evitando AbandonedMutexException. Eu usei o meu classe personalizada:

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

e usá-lo:

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

Aqui está como eu têm abordado este

Na classe do Programa: 1. Obter um System.Diagnostics.Process de seu aplicativo usando Process.GetCurrentProcess () 2. Passo através da recolha de processos abertos com o nome atual do seu aplicativo usando Process.GetProcessesByName (thisProcess.ProcessName) 3. Verifique cada process.Id contra thisProcess.Id e se uma instância já está aberto, em seguida, pelo menos 1 irá corresponder nome, mas não Id, caso contrário, continue instância abertura

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

Um toque agradável para terminar esta off seria apresentar a instância já aberto para o usuário, as chances são que eles não sabiam que estava aberta, então vamos mostrar-lhes que era. Para fazer isso eu usar user32.dll para transmitir uma mensagem para o loop de mensagens do Windows, uma mensagem personalizada, e eu tenho meu aplicativo escutar-lo no método WndProc, e se ele recebe essa mensagem, ela se apresenta para o usuário, Form .Show () ou outros enfeites

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top