André Alves de Lima

Talking about Software Development and more…

Impedindo que a aplicação seja executada mais de uma vez ao mesmo tempo

Existem cenários em que faz sentido bloquearmos a nossa aplicação de forma que o usuário não consiga executar mais de uma instância ao mesmo tempo. Até mesmo a Microsoft faz isso com algumas aplicações dela. Por exemplo, não faz sentido abrirmos o Outlook mais de uma vez ao mesmo tempo.

Como é que nós podemos fazer essa checagem na inicialização da nossa aplicação? Existem basicamente duas opções. A primeira delas é verificarmos se já existe algum processo sendo executado com o mesmo nome. Apesar de ser simples, essa opção não é lá muito eficiente. Uma outra alternativa seria bloquearmos algum recurso enquanto a nossa aplicação está sendo executada, dessa forma nós conseguimos detectar se a aplicação já está em execução ou não.

No artigo de hoje eu vou mostrar para você essas duas opções, implementadas em um projeto do tipo Console Application (mas que você pode adaptar para qualquer tipo de projeto desktop, como Windows Forms ou WPF).

Opção 1: checando o nome do processo

A maneira mais simples de detectarmos se a nossa aplicação já está sendo executada é procurarmos por algum outro processo que tenha o mesmo nome da nossa aplicação. Isso pode ser feito muito facilmente através da classe Process.

Primeiramente, temos que descobrir o nome do processo da nossa aplicação. Normalmente, o nome do processo será o nome do arquivo executável. Para descobrirmos essa informação em tempo de execução, nós utilizamos o método “GetCurrentProcess“, da classe Process. Tendo o nome do processo em mãos, nós podemos utilizar o método “GetProcessByName” para tentarmos encontrar outros processos que tenham o mesmo nome:

        // C#
        static void Main(string[] args)
        {
            var processo = System.Diagnostics.Process.GetCurrentProcess();
            var jaEstaRodando = System.Diagnostics.Process.GetProcessesByName(processo.ProcessName).Any(p => p.Id != processo.Id);
        }
    ' VB.NET
    Sub Main()
        Dim Processo = System.Diagnostics.Process.GetCurrentProcess()
        Dim JaEstaRodando = System.Diagnostics.Process.GetProcessesByName(Processo.ProcessName).Any(Function(P) P.Id <> Processo.Id)
    End Sub

Note que nós temos que filtrar o resultado para os processos que tenham um “ID” diferente do processo atual, uma vez que sempre existirá no mínimo um processo com o mesmo nome (que é justamente o processo que está sendo executado no momento da checagem).

Com isso, nós conseguimos simplesmente encerrar a nossa aplicação caso ela já esteja sendo executada. Caso contrário, nós continuamos com o fluxo normal da aplicação (que nesse exemplo imprime o texto “Instância única” no console):

            // C#
            if (jaEstaRodando)
            {
                Console.WriteLine("Já está rodando!");
                Console.ReadLine();
                return;
            }

            Console.WriteLine("Instância única");
            Console.ReadLine();
        ' VB.NET
        If (JaEstaRodando) Then
            Console.WriteLine("Já está rodando!")
            Console.ReadLine()
            Return
        End If

        Console.WriteLine("Instância única")
        Console.ReadLine()

Problemas ao checar o nome do processo

Essa opção parece muito simples e, à primeira vista, funciona muito bem. Porém, se tentarmos executar esse projeto pelo Visual Studio e, em seguida, navegarmos até a pasta “bin/debug” e rodarmos novamente o executável do projeto, qual será o resultado?

Ué, por que não funcionou? Vamos colocar um breakpoint na linha em que estamos calculando o valor para a variável “jaEstaRodando“:

Ahá! Veja só o problema. O processo que está sendo executado pelo Visual Studio na realidade não é o próprio executável, mas sim o executável com a terminação “vshost“! Esse é o comportamento padrão do Visual Studio quando executamos um projeto desktop em modo debug. Para verificarmos se esse código está realmente funcionando, teríamos que executá-lo duas vezes diretamente pelo Windows Explorer:

Porém, dessa forma nós perdemos a possibilidade de debugarmos a nossa aplicação pelo Visual Studio. Caso nós realmente precisemos debugar a aplicação enquanto testamos essa checagem de instâncias duplicadas, a alternativa seria iniciar a aplicação pelo Visual Studio sem o debugger:

E, logo em seguida, nós atachamos o debugger manualmente no nosso processo:

Como você pode perceber, essa maneira de verificarmos se a aplicação já está sendo executada tem uma forte dependência com o nome do processo. Ou seja, além das inconveniências que teremos para debugar o nosso projeto pelo Visual Studio, esse tipo de checagem pode ser facilmente burlado. Basta criarmos uma cópia do executável utilizando outro nome e pronto, está burlado o nosso algoritmo, uma vez que os executáveis teriam nomes diferentes e, por consequência, os processos não teriam o mesmo nome.

Qual seria, então, uma alternativa mais robusta para esse tipo de verificação? A classe Mutex!

Opção 2: Mutex

Se você não conhece a classe Mutex, não se preocupe, eu também não conhecia. Essa classe foi originalmente implementada para fazer a sincronização do fluxo de execução entre processos. Imagine que nós tenhamos dois processos (A e B), sendo que o processo A tem que esperar o processo B executar um cálculo antes de prosseguir com a sua execução. É esse tipo de controle de fluxo que nós conseguimos implementar utilizando a classe Mutex.

A ideia até que é bem simples. Nós criamos uma instância da classe Mutex utilizando um identificador único (pode ser uma string escolhida por você ou, melhor ainda, um GUID). Em seguida, na hora de iniciar a nossa aplicação, nós chamamos o método “WaitOne” do Mutex. Caso o retorno seja verdadeiro, isso significa que nenhum outro processo está bloqueando o Mutex, ou seja, não existe nenhum outro processo que esteja utilizando um Mutex com aquele identificador. Por fim, quando a execução da nossa aplicação for concluída, nós liberamos o Mutex através do método “ReleaseMutex“.

Veja só como é que fica o código:

            // C#
            using (var mutex = new System.Threading.Mutex(true, "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3"))
            {
                var jaEstaRodando = !mutex.WaitOne(0, true);
                if (jaEstaRodando)
                {
                    Console.WriteLine("Já está rodando!");
                    Console.ReadLine();
                    return;
                }

                Console.WriteLine("Instância única");
                Console.ReadLine();
                mutex.ReleaseMutex();
            }
        ' VB.NET
        Using Mutex = New System.Threading.Mutex(True, "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3")
            Dim JaEstaRodando = Not Mutex.WaitOne(0, True)

            If (JaEstaRodando) Then
                Console.WriteLine("Já está rodando!")
                Console.ReadLine()
                Return
            End If

            Console.WriteLine("Instância única")
            Console.ReadLine()
        End Using

E com isso nós conseguimos bloquear a execução múltipla do nosso aplicativo. Se algum usuário tentar executar a aplicação uma segunda vez, o resultado da chamada de “WaitOne” será falso, indicando que o aplicativo já está sendo executado. Essa metodologia de checagem funcionará mesmo se o usuário criar uma cópia do executável com outro nome, uma vez que o identificador do Mutex continuará idêntico independente do nome do processo ou executável.

Nota: no código acima eu utilizei um GUID como identificador do Mutex, que é um tipo de identificador com probabilidade baixíssima de ser duplicado (veja mais informações na Wikipedia). Você pode gerar GUIDs utilizando ferramentas online (como essa aqui) ou diretamente pelo Visual Studio, através da opção “Tools -> Create GUID”.

E no Terminal Services?

Tudo isso funciona perfeitamente caso a nossa aplicação rode diretamente no computador do cliente. Porém, em cenários onde a nossa aplicação estiver rodando através do Terminal Services (ou outros serviços desse tipo, como Citrix MetaFrame / XenApp), essa metodologia vai por água abaixo.

Uma vez que a nossa aplicação esteja sendo executada em um servidor de aplicações, é óbvio que múltiplas instâncias serão executadas ao mesmo tempo no mesmo servidor. Se implementarmos qualquer uma das duas soluções apresentadas até agora neste artigo, o primeiro usuário que executar a nossa aplicação bloqueará a execução para todos os outros usuários subsequentes.

Em cenários como esse, o ideal é que a checagem seja feita por usuário. Ou seja, cada usuário só poderá abrir uma única instância da nossa aplicação. Para fazermos isso, nós temos que levar em conta o nome do usuário na hora de verificarmos se a aplicação já está sendo executada.

Se optarmos por fazer a checagem pelo nome do processo, nós teríamos que descobrir o nome do usuário que iniciou cada processo. Isso pode ser feito através de uma consulta nos objetos de gerenciamento do Windows (Management Objects), conforme indicado nesta thread do StackOverflow:

        // C#
        public static string GetProcessOwner(int processId)
        {
            var query = "Select * From Win32_Process Where ProcessID = " + processId;
            var searcher = new System.Management.ManagementObjectSearcher(query);
            var processList = searcher.Get();

            foreach (System.Management.ManagementObject obj in processList)
            {
                string[] argList = new string[] { string.Empty, string.Empty };
                int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
                if (returnVal == 0)
                {
                    // return DOMAIN\user
                    return argList[1] + "\\" + argList[0];
                }
            }

            return "NO OWNER";
        }
    ' VB.NET
    Public Function GetProcessOwner(ProcessId As Integer) As String
        Dim Query = "Select * From Win32_Process Where ProcessID = " + ProcessId
        Dim Searcher = New System.Management.ManagementObjectSearcher(Query)
        Dim ProcessList = Searcher.[Get]()

        For Each Obj As System.Management.ManagementObject In ProcessList
            Dim ArgList As String() = New String() {String.Empty, String.Empty}
            Dim ReturnVal As Integer = Convert.ToInt32(Obj.InvokeMethod("GetOwner", ArgList))
            If ReturnVal = 0 Then
                ' return DOMAIN\user
                Return ArgList(1) + "\" + ArgList(0)
            End If
        Next

        Return "NO OWNER"
    End Function

Nota: algumas classes utilizadas no método acima dependem do assembly “System.Management”, que não é referenciado por padrão pelo Visual Studio em novos projetos. Não esqueça de adicionar uma referência para esse assembly no seu projeto, caso contrário você não conseguirá compilar a solução:

Com esse método copiado para o nosso projeto, nós podemos utilizá-lo para checarmos também o nome do usuário na hora de verificarmos se o processo já está sendo executado:

// C#
var jaEstaRodando = System.Diagnostics.Process.GetProcessesByName(processo.ProcessName).Any(p => p.Id != processo.Id && GetProcessOwner(p.Id) == GetProcessOwner(processo.Id));
' VB.NET
Dim JaEstaRodando = System.Diagnostics.Process.GetProcessesByName(Processo.ProcessName).Any(Function(P) P.Id <> Processo.Id And GetProcessOwner(P.Id) = GetProcessOwner(Processo.Id))

Pronto! Com essas alterações a nossa checagem pelo nome do processo também levará em conta o nome do usuário que iniciou o processo. Ou seja, nós só permitiremos que cada usuário rode somente uma instância da aplicação ao mesmo tempo.

Por fim, caso optemos pela alternativa do Mutex, a única coisa que temos que fazer é concatenarmos o nome do usuário no identificador do Mutex. Dessa forma, o identificador será único somente no contexto de cada usuário:

            // C#
            var identificadorMutex = string.Format("{0}_{1}", "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3", System.Security.Principal.WindowsIdentity.GetCurrent().User);
            using (var mutex = new System.Threading.Mutex(true, identificadorMutex))
            {
               // O resto do código fica o mesmo
            }
        ' VB.NET
        Dim IdentificadorMutex = String.Format("{0}_{1}", "A403A6EB-6472-4B42-B5C1-C0E06F9F25B3", System.Security.Principal.WindowsIdentity.GetCurrent().User)
        Using Mutex = New System.Threading.Mutex(True, IdentificadorMutex)
            ' O resto do código fica o mesmo
        End Using

Opção bônus para VB.NET + Windows Forms

Edit: esta seção foi adicionada depois da publicação do artigo original, com base na sugestão de um leitor (o Dario Ferreira Franca).

Eu nem sabia da existência dessa funcionalidade, mas se o seu projeto for do tipo “Windows Forms” utilizando a linguagem VB.NET, o próprio Visual Studio já conta com uma opção que previne que a aplicação seja executada mais de uma vez ao mesmo tempo. Na página de propriedades do seu projeto Windows Forms, marque a opção “Make single instance application” e pronto! O usuário não conseguirá executar mais de uma instância da sua aplicação ao mesmo tempo:

E como é que esse controle é feito por trás dos panos pelo VB.NET? Por curiosidade, se decompilarmos uma aplicação Windows Forms desenvolvida com VB.NET, notaremos que a sua inicialização é um pouco diferente do C#. No VB.NET, a classe da aplicação herda de Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase, e essa classe já implementa um mecanismo de “instância única” (propriedade “IsSingleInstance“). A codificação da “instância única” nesse caso se dá através de um EventWaitHandle, implementado como se fosse um semáforo. É basicamente a mesma ideia de implementação do Mutex que vimos anteriormente aqui no artigo:

Então fica a dica caso a sua aplicação seja Windows Forms com VB.NET.

Concluindo

Em algumas situações não faz sentido o usuário executar a nossa aplicação mais de uma vez ao mesmo tempo. Nesses casos, a recomendação é implementarmos uma checagem na nossa aplicação, que não permita que um mesmo usuário execute a aplicação duas ou mais vezes ao mesmo tempo.

No artigo de hoje você aprendeu duas estratégias que podemos utilizar para implementarmos essa checagem. A primeira delas utiliza o como base o nome do processo. Já na segunda metodologia, nós utilizamos uma instância da classe Mutex construída com um identificador único.

Como você pode observar ao longo do artigo, a checagem através do nome do processo não é a mais confiável. Esse algoritmo pode ser facilmente burlado se o usuário simplesmente fizer uma cópia do executável da aplicação utilizando outro nome. Portanto, a opção mais recomendada para verificarmos se a nossa aplicação já está sendo executada é através da classe Mutex.

Por fim, você conferiu também os problemas que podemos enfrentar caso a nossa aplicação esteja sendo executada em um servidor de aplicações (como o Terminal Services). Nesse caso, temos que levar em consideração também o nome do usuário que está executando a aplicação.

E você, já precisou implementar uma checagem como essa? Qual das alternativas você utilizou na sua aplicação? Você enfrentou algum problema específico que não foi abordado neste artigo? Conte mais detalhes nos comentários logo abaixo!

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Até a próxima!

André Lima

Icon by Pixabay used under Creative Commons
https://pixabay.com/en/menu-gui-interface-template-ui-303121/

Newsletter do André Lima

* indicates required



Powered by MailChimp

7 thoughts on “Impedindo que a aplicação seja executada mais de uma vez ao mesmo tempo

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *