Impedire più istanze di una determinata app in .NET?


123

In .NET, qual è il modo migliore per impedire l'esecuzione contemporanea di più istanze di un'app? E se non esiste una tecnica "migliore", quali sono alcuni degli avvertimenti da considerare con ciascuna soluzione?

Risposte:


151

Usa Mutex. Uno degli esempi precedenti utilizzando GetProcessByName ha molti avvertimenti. Ecco un buon articolo sull'argomento:

http://odetocode.com/Blogs/scott/archive/2004/08/20/401.aspx

[STAThread]
static void Main() 
{
   using(Mutex mutex = new Mutex(false, "Global\\" + appGuid))
   {
      if(!mutex.WaitOne(0, false))
      {
         MessageBox.Show("Instance already running");
         return;
      }

      Application.Run(new Form1());
   }
}

private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

1
L'uso di un mutex funziona anche per il codice non .net (sebbene la sintassi varierebbe)
crashmstr

9
Ecco una versione leggermente più completa, con alcuni buoni commenti: stackoverflow.com/questions/229565/…
Richard Watson

2
@ClarkKent: solo una stringa casuale in modo che il nome Mutex non collida con il nome di un'altra applicazione.
jgauffin

Questo può limitarsi anche a un certo numero di istanze?
Alejandro del Río

4
Per avere un controllo migliore durante il controllo delle versioni o la modifica della guida dell'app, puoi usare: string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), true)[0]).Value;che otterrà il guid dell'assembly in esecuzione
ciosoriog

22
if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
{
  AppLog.Write("Application XXXX already running. Only one instance of this application is allowed", AppLog.LogMessageType.Warn);
  return;
}

1
Penso che dovrebbe vedere questo: odetocode.com/blogs/scott/archive/2004/08/20/…
SubmarineX

@SubmarineX Come sei sicuro che questo articolo sia corretto? è ancora polemica tra mutex e codeproject.com/Articles/4975/…
John Nguyen

Incredibile Grazie semplicemente perfetto per il tuo tempo e i tuoi sforzi
Ahmed Mahmoud

2
-1 Sebbene questa sia la soluzione rapida, è anche molto facile aggirare a.exe che potrebbe essere rinominato in b.exe ed entrambi verranno eseguiti. + altri casi in cui questo semplicemente non funzionerà come previsto. Usare con cautela!
Lars Nielsen

Questo non funziona con il mono-sviluppo. In ambienti Windows però funziona benissimo.
Tono Nam,

20

Ecco il codice di cui hai bisogno per assicurarti che sia in esecuzione una sola istanza. Questo è il metodo per utilizzare un mutex denominato.

public class Program
{
    static System.Threading.Mutex singleton = new Mutex(true, "My App Name");

    static void Main(string[] args)
    {
        if (!singleton.WaitOne(TimeSpan.Zero, true))
        {
            //there is already another instance running!
            Application.Exit();
        }
    }
}

2
Per un'applicazione WPF utilizzare Application.Current.Shutdown (); Questo metodo funziona come un incantesimo. Grazie Terrapin.
Jeff

Qui la cosa principale è rendere statico il mutex. Così come nell'altro caso GC lo ritirerà.
Oleksii

Ho apprezzato la semplicità e la chiarezza del Mutex denominato. Questo codice è conciso ed efficace.
Ciad,

7

Hanselman ha un post sull'utilizzo della classe WinFormsApplicationBase dall'assembly Microsoft.VisualBasic per eseguire questa operazione.


Lo uso da un paio d'anni, ma ora sto cercando di passare a una soluzione basata su Mutex. Ho clienti che segnalano problemi con questo e sospetto che stia utilizzando Remoting per farlo.
Richard Watson

5

Sembra che ci siano 3 tecniche fondamentali che sono state suggerite finora.

  1. Deriva dalla classe Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase e imposta la proprietà IsSingleInstance su true. (Credo che un avvertimento qui sia che questo non funzionerà con le applicazioni WPF, vero?)
  2. Usa un mutex con nome e controlla se è già stato creato.
  3. Ottieni un elenco dei processi in esecuzione e confronta i nomi dei processi. (Questo ha l'avvertenza di richiedere che il nome del processo sia univoco rispetto a qualsiasi altro processo in esecuzione sulla macchina di un determinato utente.)

Qualche avvertimento che ho perso?


3
Non credo che 3 sia molto efficiente. Voterei per il Mutex, l'ho usato senza problemi molte volte. Non ho mai usato l'elemento 1, non sono sicuro di come funzioni quando sei in c #.
typematching

2
L'opzione 1 funziona ancora con WPF è solo leggermente più complicata. msdn.microsoft.com/en-us/library/ms771662.aspx
Graeme Bradbury,

5

1 - Crea un riferimento in program.cs ->

using System.Diagnostics;

2 - Metti in void Main()come prima riga di codice ->

 if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length >1)
                return;

Questo è tutto.


Qual è la differenza di questo Mutex? C'è un problema?
Elicottero d'attacco Harambe

Sta usando il nome del processo. Se il nome viene ripetuto, genererebbe una falsa bandiera. In ogni altro caso, è più pulito del mutex
magallanes il

4

Utilizzando Visual Studio 2005 o 2008 quando crei un progetto per un eseguibile, nelle finestre delle proprietà all'interno del pannello "Applicazione" è presente una casella di controllo denominata "Crea applicazione a istanza singola" che puoi attivare per convertire l'applicazione in un'applicazione a istanza singola .

Ecco un'acquisizione della finestra di cui sto parlando: inserisci qui la descrizione dell'immagine Questo è un progetto di applicazione Windows di Visual Studio 2008.


3
Ho cercato questa casella di controllo che hai menzionato, per la mia app C # / WPF, e non ce n'è.
HappyNomad

3
Non lo vedo nemmeno nelle proprietà della mia app VS 2008 C # / WinForms.
Jesse McGrew

Non anche in VS2005. Deve menzionare il vecchio studio VB.
nawfal

Sì, l'opzione esiste, ho modificato il post per aggiungere un'acquisizione della finestra in cui puoi trovare questa opzione.
Doliveras

6
L'opzione esiste solo per l'applicazione VB.NET, non per C #. Apparentemente l'opzione stessa utilizza la classe WinFormsApplicationBase dall'assembly Microsoft.VisualBasic.
amolbk

4

Ho provato tutte le soluzioni qui e niente ha funzionato nel mio progetto C # .net 4.0. Spero di aiutare qualcuno qui la soluzione che ha funzionato per me:

Come variabili di classe principali:

private static string appGuid = "WRITE AN UNIQUE GUID HERE";
private static Mutex mutex;

Quando è necessario verificare se l'app è già in esecuzione:

bool mutexCreated;
mutex = new Mutex(true, "Global\\" + appGuid, out mutexCreated);
if (mutexCreated)
    mutex.ReleaseMutex();

if (!mutexCreated)
{
    //App is already running, close this!
    Environment.Exit(0); //i used this because its a console app
}

Avevo bisogno di chiudere altre istanze solo con alcune condizioni, questo ha funzionato bene per il mio scopo


Non è necessario acquisire effettivamente il mutex e rilasciarlo. Tutto quello che devi sapere è se un'altra app ha già creato l'oggetto (cioè, il suo conteggio dei riferimenti al kernel> = 1).
Michael Goldshteyn


3

Dopo aver provato più soluzioni ho la domanda. Ho finito per usare l'esempio per WPF qui: http://www.c-sharpcorner.com/UploadFile/f9f215/how-to-restrict-the-application-to-just-one-instance/

public partial class App : Application  
{  
    private static Mutex _mutex = null;  

    protected override void OnStartup(StartupEventArgs e)  
    {  
        const string appName = "MyAppName";  
        bool createdNew;  

        _mutex = new Mutex(true, appName, out createdNew);  

        if (!createdNew)  
        {  
            //app is already running! Exiting the application  
            Application.Current.Shutdown();  
        }  

    }          
}  

In App.xaml:

x:Class="*YourNameSpace*.App"
StartupUri="MainWindow.xaml"
Startup="App_Startup"


2

Usa VB.NET! No davvero ;)

using Microsoft.VisualBasic.ApplicationServices;

WindowsFormsApplicationBase di VB.Net fornisce una proprietà "SingleInstace", che determina altre istanze e consente l'esecuzione di una sola istanza.


2

Questo è il codice per VB.Net

Private Shared Sub Main()
    Using mutex As New Mutex(False, appGuid)
        If Not mutex.WaitOne(0, False) Then
              MessageBox.Show("Instance already running", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        Application.Run(New Form1())
    End Using
End Sub

Questo è il codice per C #

private static void Main()
{
    using (Mutex mutex = new Mutex(false, appGuid)) {
        if (!mutex.WaitOne(0, false)) {
            MessageBox.Show("Instance already running", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

        Application.Run(new Form1());
    }
}



1

(Nota: questa è una soluzione divertente! Funziona ma utilizza un cattivo design GDI + per raggiungere questo obiettivo.)

Inserisci un'immagine nella tua app e caricala all'avvio. Tienilo premuto fino alla chiusura dell'app. L'utente non sarà in grado di avviare una seconda istanza. (Ovviamente la soluzione mutex è molto più pulita)

private static Bitmap randomName = new Bitmap("my_image.jpg");

Questo è davvero geniale nella sua semplicità e funziona praticamente con qualsiasi tipo di file e non solo con le immagini. Mi sembra che la soluzione Mutex sia tutt'altro che "pulita". È estremamente complicato e apparentemente ci sono molti modi in cui fallisce perché non lo fa "bene". Richiede anche un Main()metodo che va contro il modo in cui dovrebbe funzionare WPF.
Kyle Delaney

Questo è un po 'come l'utilizzo di bug. Funziona ma non è fatto per quello scopo. Non lo userei come professionista.
Bitterblue

Sì, è un peccato che non abbiamo una soluzione così efficace e semplice senza fare affidamento su eccezioni.
Kyle Delaney

Anche se non è esattamente un bug. .NET si comporta ancora come previsto.
Kyle Delaney

1
[STAThread]
static void Main()                  // args are OK here, of course
{
    bool ok;
    m = new System.Threading.Mutex(true, "YourNameHere", out ok);

    if (! ok)
    {
        MessageBox.Show("Another instance is already running.");
        return;
    }

    Application.Run(new Form1());   // or whatever was there

    GC.KeepAlive(m);                // important!
}

Da: garantire una singola istanza dell'applicazione .NET

e: Mutex dell'applicazione a istanza singola

Stessa risposta di @Smink e @Imjustpondering con una svolta:

Domande frequenti di Jon Skeet su C # per scoprire perché GC.KeepAlive è importante


-1, perché non riesci a utilizzare un blocco using con il mutex, il che renderebbe superfluo KeepAlive. E sì, penso che John Skeet abbia sbagliato. Non spiega perché sarebbe sbagliato smaltire il mutex in questo caso.

1

Semplicemente usando un StreamWriter, che ne dici di questo?

System.IO.File.StreamWriter OpenFlag = null;   //globally

e

try
{
    OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
    Environment.Exit(0);
}

0

Normalmente è fatto con un Mutex denominato (usa il nuovo Mutex ("nome della tua app", true) e controlla il valore restituito), ma ci sono anche alcune classi di supporto in Microsoft.VisualBasic.dll che possono farlo per te .


0

Questo ha funzionato per me in puro C #. il try / catch è quando forse un processo nell'elenco esce durante il tuo ciclo.

using System.Diagnostics;
....
[STAThread]
static void Main()
{
...
        int procCount = 0;
        foreach (Process pp in Process.GetProcesses())
        {
            try
            {
                if (String.Compare(pp.MainModule.FileName, Application.ExecutablePath, true) == 0)
                {
                    procCount++;                        
                    if(procCount > 1) {
                       Application.Exit();
                       return;
                    }
                }
            }
            catch { }
        }
        Application.Run(new Form1());
}

0

Assicurati di considerare la sicurezza quando limiti un'applicazione a una singola istanza:

Articolo completo: https://blogs.msdn.microsoft.com/oldnewthing/20060620-13/?p=30813

Stiamo utilizzando un mutex denominato con un nome fisso per rilevare se è in esecuzione un'altra copia del programma. Ma ciò significa anche che un utente malintenzionato può creare prima il mutex, impedendo in tal modo l'esecuzione del nostro programma! Come posso prevenire questo tipo di attacco Denial of Service?

...

Se l'aggressore è in esecuzione nello stesso contesto di sicurezza in cui è (o sarebbe) in esecuzione il tuo programma, non c'è niente che puoi fare. Qualunque sia la "stretta di mano segreta" che si crea per determinare se è in esecuzione un'altra copia del programma, l'aggressore può imitarla. Dal momento che è in esecuzione nel contesto di sicurezza corretto, può fare tutto ciò che il programma "reale" può fare.

...

Chiaramente non puoi proteggerti da un utente malintenzionato in esecuzione con lo stesso privilegio di sicurezza, ma puoi comunque proteggerti da aggressori senza privilegi che eseguono altri privilegi di sicurezza.

Prova a impostare un DACL sul tuo mutex, ecco il modo .NET: https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.mutexsecurity(v=vs.110).aspx


0

Nessuna di queste risposte ha funzionato per me perché avevo bisogno che funzionasse sotto Linux usando monodevelop. Questo funziona alla grande per me:

Chiama questo metodo passandogli un ID univoco

    public static void PreventMultipleInstance(string applicationId)
    {
        // Under Windows this is:
        //      C:\Users\SomeUser\AppData\Local\Temp\ 
        // Linux this is:
        //      /tmp/
        var temporaryDirectory = Path.GetTempPath();

        // Application ID (Make sure this guid is different accross your different applications!
        var applicationGuid = applicationId + ".process-lock";

        // file that will serve as our lock
        var fileFulePath = Path.Combine(temporaryDirectory, applicationGuid);

        try
        {
            // Prevents other processes from reading from or writing to this file
            var _InstanceLock = new FileStream(fileFulePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
            _InstanceLock.Lock(0, 0);
            MonoApp.Logger.LogToDisk(LogType.Notification, "04ZH-EQP0", "Aquired Lock", fileFulePath);

            // todo investigate why we need a reference to file stream. Without this GC releases the lock!
            System.Timers.Timer t = new System.Timers.Timer()
            {
                Interval = 500000,
                Enabled = true,
            };
            t.Elapsed += (a, b) =>
            {
                try
                {
                    _InstanceLock.Lock(0, 0);
                }
                catch
                {
                    MonoApp.Logger.Log(LogType.Error, "AOI7-QMCT", "Unable to lock file");
                }
            };
            t.Start();

        }
        catch
        {
            // Terminate application because another instance with this ID is running
            Environment.Exit(102534); 
        }
    }         
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.