742 Stimmen

Was ist der richtige Weg, um eine einzelne Instanz WPF-Anwendung zu erstellen?

Verwendung von C# und WPF unter .NET (anstelle von Windows-Formulare oder Konsole), was ist der richtige Weg, um eine Anwendung zu erstellen, die nur als eine einzige Instanz ausgeführt werden kann?

Ich weiß, dass es etwas mit einem mythischen Ding namens Mutex zu tun hat, aber selten finde ich jemanden, der sich die Mühe macht, anzuhalten und zu erklären, was so etwas ist.

Der Code muss auch die bereits laufende Instanz darüber informieren, dass der Benutzer versucht hat, eine zweite Instanz zu starten, und vielleicht auch alle Befehlszeilenargumente übergeben, falls welche vorhanden sind.

17 Stimmen

Gibt die CLR nicht automatisch alle nicht freigegebenen Mutexe frei, wenn die Anwendung ohnehin beendet wird?

2 Stimmen

@Cocowalla: Der Finalisierer sollte die nicht verwalteten Mutexe entsorgen, es sei denn, er kann nicht wissen, ob die Mutex von der verwalteten Anwendung erstellt oder an eine bestehende angehängt wurde.

1 Stimmen

Es ist sinnvoll, nur eine Instanz Ihrer Anwendung zu haben. Aber die Übergabe von Argumenten an eine bereits existierende Anwendung erscheint mir ein wenig albern. Ich kann keinen Grund dafür sehen, dies zu tun. Wenn Sie eine Anwendung mit einer Dateierweiterung verknüpfen, sollten Sie so viele Anwendungen öffnen, wie Benutzer Dokumente öffnen wollen. Das ist das Standardverhalten, das jeder Benutzer erwarten würde.

612voto

Matt Davis Punkte 44077

Hier ist eine sehr gute Artikel bezüglich der Mutex-Lösung. Der in dem Artikel beschriebene Ansatz ist aus zwei Gründen vorteilhaft.

Erstens erfordert es keine Abhängigkeit von der Microsoft.VisualBasic-Assembly. Wenn mein Projekt bereits eine Abhängigkeit von dieser Assembly hätte, würde ich wahrscheinlich die folgende Vorgehensweise empfehlen in einer anderen Antwort gezeigt . Aber wie es ist, verwende ich nicht die Microsoft.VisualBasic-Assembly, und ich würde lieber nicht eine unnötige Abhängigkeit zu meinem Projekt hinzufügen.

Zweitens zeigt der Artikel, wie man die vorhandene Instanz der Anwendung in den Vordergrund bringt, wenn der Benutzer versucht, eine andere Instanz zu starten. Das ist eine sehr nette Idee, die die anderen hier beschriebenen Mutex-Lösungen nicht bieten.


UPDATE

Mit Stand vom 1.8.2014 ist der oben verlinkte Artikel immer noch aktiv, aber der Blog wurde schon seit einiger Zeit nicht mehr aktualisiert. Das lässt mich befürchten, dass er irgendwann verschwinden könnte, und mit ihm die vorgeschlagene Lösung. Ich gebe den Inhalt des Artikels hier für die Nachwelt wieder. Der Text gehört ausschließlich dem Eigentümer des Blogs unter Sanity Free Coding .

Heute wollte ich einige Codes umstrukturieren, die meine Anwendung mehrere Instanzen von sich selbst auszuführen.

Zuvor hatte ich verwendet System.Diagnostik.Prozess für die Suche nach einem Instanz von myapp.exe in der Prozessliste zu suchen. Das funktioniert zwar, aber es bringt eine Menge Overhead mit sich, und ich wollte etwas Saubereres.

Da ich wusste, dass ich dafür eine Mutex verwenden konnte (aber es noch nie getan hatte) ), habe ich mich daran gemacht, meinen Code zu reduzieren und mein Leben zu vereinfachen.

In der Klasse meiner Anwendung main habe ich eine statische Klasse namens Mutex :

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

Mit einem benannten Mutex können wir die Synchronisierung über mehrere mehrere Threads und Prozesse, was genau die Magie ist, die ich suche suche.

Mutex.WaitOne hat eine Überladung, die eine Zeitspanne angibt, die wir warten sollen. Da wir unseren Code nicht wirklich synchronisieren wollen synchronisieren wollen (sondern nur prüfen, ob er gerade in Gebrauch ist), verwenden wir die Überladung mit zwei Parametern: Mutex.WaitOne(Zeitspanne timeout, bool exitContext) . Wait one gibt true zurück, wenn er in der Lage ist, einzutreten, und false, wenn er nicht eingetreten ist. In diesem Fall wollen wir überhaupt nicht warten; wenn unsere Mutex gerade benutzt wird, überspringen wir ihn und machen weiter, also übergeben wir TimeSpan.Zero (warte 0 Millisekunden), und setzen den exitContext auf true, damit wir den Synchronisationskontext verlassen können, bevor wir versuchen, eine Sperre darauf zu erhalten. Mit verpacken wir unseren Application.Run-Code in etwas wie dieses:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

Wenn unsere Anwendung also läuft, gibt WaitOne false zurück, und wir erhalten eine Nachrichtenfeld.

Anstatt ein Meldungsfenster anzuzeigen, habe ich mich für ein kleines Win32-Tool entschieden um meine laufende Instanz zu benachrichtigen, dass jemand vergessen hat, dass sie bereits dass jemand vergessen hat, dass es bereits läuft (indem es sich an die Spitze aller anderen Fenster setzt). Um dies zu erreichen, habe ich PostMessage um eine benutzerdefinierte Nachricht an alle Fenster zu senden (die benutzerdefinierte Nachricht wurde mit RegisterWindowMessage meiner laufenden Anwendung, was bedeutet, dass nur meine Anwendung weiß, was ist), dann wird meine zweite Instanz beendet. Die laufende Anwendungsinstanz erhält diese Benachrichtigung und verarbeitet sie. Um das zu tun, habe ich außer Kraft gesetzt. WndProc in meinem Hauptformular und hörte für meine benutzerdefinierte Benachrichtigung. Als ich diese Benachrichtigung erhielt, setzte ich die TopMost-Eigenschaft des Formulars auf true gesetzt, um es ganz nach oben zu bringen.

Ich habe folgendes Ergebnis erzielt:

  • Programm.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs (Vorderseite teilweise)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}

9 Stimmen

Auf der Grundlage, dass diese Antwort weniger Code und weniger Bibliotheken verwendet und die höchste Funktionalität bietet, werde ich dies zur neuen akzeptierten Antwort machen. Wenn jemand einen korrekteren Weg kennt, um das Formular mithilfe von APIs nach oben zu bringen, kann er das gerne hinzufügen.

0 Stimmen

Ich bin mir nicht sicher, ob ich das verstehe - warum Native Messages verwenden? Dafür sind doch Ereignisse da... (wenn es um die Entkopplung geht, sollte man wirklich cab oder EreignisBroker ...)

13 Stimmen

@BlueRaja, Sie starten die erste App-Instanz. Wenn Sie die zweite App-Instanz starten, stellt sie fest, dass bereits eine andere Instanz läuft und bereitet sich auf das Herunterfahren vor. Bevor sie dies tut, sendet sie eine native "SHOWME"-Nachricht an die erste Instanz, wodurch die erste Instanz an die Spitze gebracht wird. Ereignisse in .NET erlauben keine prozessübergreifende Kommunikation, weshalb die systemeigene Nachricht verwendet wird.

123voto

Dale Ragan Punkte 18091

Sie könnten die Mutex-Klasse verwenden, aber Sie werden bald herausfinden, dass Sie den Code zur Übergabe der Argumente usw. selbst implementieren müssen. Nun, ich habe einen Trick bei der Programmierung in WinForms gelernt, als ich las Das Buch von Chris Sell . Dieser Trick verwendet eine Logik, die uns bereits im Framework zur Verfügung steht. Ich weiß nicht, wie es Ihnen geht, aber wenn ich etwas erfahre, das ich im Framework wiederverwenden kann, nehme ich normalerweise diesen Weg, anstatt das Rad neu zu erfinden. Es sei denn natürlich, es tut nicht alles, was ich will.

Wenn ich in WPF bekam, kam ich mit einer Möglichkeit, den gleichen Code zu verwenden, aber in einer WPF-Anwendung. Diese Lösung sollte Ihre Bedürfnisse auf der Grundlage Ihrer Frage erfüllen.

Zunächst müssen wir unsere Anwendungsklasse erstellen. In dieser Klasse werden wir das OnStartup-Ereignis außer Kraft setzen und eine Methode namens Activate erstellen, die später verwendet wird.

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

Zweitens müssen wir eine Klasse erstellen, die unsere Instanzen verwalten kann. Bevor wir das tun, werden wir etwas Code aus der Microsoft.VisualBasic-Assembly wiederverwenden. Da ich in diesem Beispiel C# verwende, musste ich einen Verweis auf die Baugruppe erstellen. Wenn Sie VB.NET verwenden, müssen Sie nichts tun. Die Klasse, die wir verwenden werden, ist WindowsFormsApplicationBase, von der wir unseren Instanzmanager erben und dann die Eigenschaften und Ereignisse nutzen, um die einzelne Instanzierung zu behandeln.

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

Im Grunde verwenden wir die VB-Bits, um einzelne Instanzen zu erkennen und entsprechend zu verarbeiten. OnStartup wird ausgelöst, wenn die erste Instanz geladen wird. OnStartupNextInstance wird abgefeuert, wenn die Anwendung erneut ausgeführt wird. Wie Sie sehen können, kann ich über die Ereignisargumente auf das zugreifen, was in der Befehlszeile übergeben wurde. Ich habe den Wert auf ein Instanzfeld gesetzt. Sie können die Befehlszeile hier analysieren oder sie über den Konstruktor und den Aufruf der Activate-Methode an Ihre Anwendung übergeben.

Drittens ist es an der Zeit, unseren EntryPoint zu erstellen. Anstatt die Anwendung neu zu erstellen, wie Sie es normalerweise tun würden, werden wir die Vorteile unseres SingleInstanceManagers nutzen.

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

Nun, ich hoffe, Sie können alles nachvollziehen und diese Umsetzung nutzen und sich zu eigen machen.

0 Stimmen

Das ist die Art und Weise, wie wir es tun, und ich war nie besonders glücklich darüber, weil ich von WinForms abhängig bin.

15 Stimmen

Ich würde bei der Mutex-Lösung bleiben, weil sie nichts mit Formularen zu tun hat.

3 Stimmen

Ich habe dies verwendet, weil ich Probleme mit anderen Ansätzen hatte, aber ich bin mir ziemlich sicher, dass es unter der Haube Remoting verwendet. Meine Anwendung hatte zwei damit zusammenhängende Probleme: Einige Kunden behaupten, dass sie versucht, nach Hause zu telefonieren, obwohl sie ihr gesagt haben, dass sie das nicht darf. Wenn sie genauer hinsehen, ist die Verbindung zu localhost. Trotzdem wissen sie das zunächst nicht. Außerdem kann ich Remoting nicht für einen anderen Zweck verwenden (denke ich?), weil es bereits für diesen Zweck verwendet wird. Als ich den Mutex-Ansatz ausprobierte, konnte ich Remoting wieder verwenden.

96voto

jason saldo Punkte 9556

De aquí .

Eine häufige Verwendung für eine prozessübergreifende Mutex ist es, sicherzustellen, dass nur eine Instanz eines Programms zur gleichen Zeit laufen kann. So wird es gemacht:

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Eine gute Eigenschaft von Mutex ist, dass die CLR den Mutex automatisch freigibt, wenn die Anwendung beendet wird, ohne dass zuvor ReleaseMutex aufgerufen wurde.

7 Stimmen

Ich muss sagen, ich mag diese Antwort viel mehr als die akzeptierte, einfach aufgrund der Tatsache, dass es nicht abhängig von WinForms ist. Persönlich die meisten meiner Entwicklung hat sich auf WPF bewegt und ich möchte nicht in WinForm-Bibliotheken für so etwas wie dieses ziehen müssen.

8 Stimmen

Um eine vollständige Antwort zu geben, müssen Sie natürlich auch die Übergabe der Argumente an die andere Instanz beschreiben :)

1 Stimmen

@Jason, gut, danke! Aber ich ziehe es vor, keine Zeitüberschreitung zuzulassen. Das ist sehr subjektiv und hängt von so vielen Variablen ab. Wenn Sie jemals eine andere Anwendung starten wollen, geben Sie einfach Ihren Mutex schneller frei, z.B. sobald der Benutzer das Schließen bestätigt.

68voto

Simon_Weaver Punkte 129442

MSDN hat eine Beispielanwendung sowohl für C# als auch für VB, um genau dies zu tun: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Die gebräuchlichste und zuverlässigste Technik für die Entwicklung von Einzelinstanz Erkennung ist die Verwendung der Microsoft .NET Framework Remoting-Infrastruktur (System.Remoting). Das Microsoft .NET Framework (Version 2.0) enthält einen Typ, WindowsFormsApplicationBase, der die erforderliche Remoting-Funktionalität kapselt. Zur Einbindung diesen Typ in eine WPF-Anwendung einzubinden, muss ein Typ von ihm abgeleitet werden und als Zwischenschicht zwischen der Anwendung statischen Einstiegspunktmethode, Main, und der Anwendung der WPF-Anwendung Application Typ verwendet werden. Das Shim erkennt, wenn eine Anwendung zum ersten Mal gestartet wird, und wenn nachfolgende Starts versucht werden versucht werden, und gibt die Steuerung der WPF Anwendungstyp, um zu bestimmen, wie Verarbeitung der Starts.

  • Für C#-Leute einfach einen tiefen Atemzug nehmen und vergessen Sie die ganze "Ich will nicht VisualBasic DLL einschließen". Wegen der ce und was Scott Hanselman sagt und die Tatsache, dass dies so ziemlich die sauberste Lösung für das Problem ist und von Leuten entwickelt wurde, die viel mehr über das Framework wissen als Sie.
  • Aus Sicht der Benutzerfreundlichkeit ist es so, dass, wenn der Benutzer eine Anwendung lädt und diese bereits geöffnet ist, er eine Fehlermeldung erhält wie 'Another instance of the app is running. Bye' dann werden sie keine sehr glücklichen Nutzer sein. Sie MÜSSEN (in einer GUI-Anwendung) einfach zu dieser Anwendung wechseln und die angegebenen Argumente eingeben - oder, wenn Befehlszeilenparameter keine Bedeutung haben, müssen Sie die Anwendung, die möglicherweise minimiert wurde, aufrufen.

Der Rahmen hat bereits Unterstützung für diese - es ist nur, dass irgendein Idiot die DLL benannt Microsoft.VisualBasic und es wurde nicht in die Microsoft.ApplicationUtils oder etwas Ähnliches. Überwinden Sie es - oder öffnen Sie Reflector.

Tipp: Wenn Sie diesen Ansatz so verwenden, wie er ist, und Sie bereits eine App.xaml mit Ressourcen usw. haben, müssen Sie Sehen Sie sich auch dies an .

0 Stimmen

Vielen Dank für den Link "Schauen Sie sich auch das an". Das ist genau das, was ich brauchte. Übrigens ist die Lösung Nr. 3 in Ihrem Link die beste.

0 Stimmen

Ich bin auch ein Verfechter des Delegierens an das Framework und speziell entwickelte Bibliotheken, wo immer dies möglich ist.

27voto

CharithJ Punkte 44196

Dieser Code sollte in die Hauptmethode eingefügt werden. Sehen Sie sich aquí für weitere Informationen über die main-Methode in WPF.

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

Methode 2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

Anmerkung: Die obigen Methoden setzen voraus, dass Ihr Prozess/Ihre Anwendung einen eindeutigen Namen hat. Denn es verwendet den Prozessnamen, um festzustellen, ob es bereits Prozessoren gibt. Wenn Ihre Anwendung also einen sehr gebräuchlichen Namen hat (z.B.: Notepad), wird der obige Ansatz nicht funktionieren.

3 Stimmen

Außerdem funktioniert dies nicht, wenn auf Ihrem Computer ein anderes Programm mit demselben Namen läuft. ProcessName gibt den Namen der ausführbaren Datei abzüglich des exe . Wenn Sie eine Anwendung mit dem Namen "Notepad" erstellen und der Windows-Notepad läuft, wird er als Ihre laufende Anwendung erkannt.

2 Stimmen

Vielen Dank für diese Antwort. Ich habe so viele ähnliche Fragen gefunden und die Antworten waren immer so kompliziert und/oder verwirrend, dass ich sie für nutzlos hielt. Diese (Methode #1) ist einfach, klar und vor allem hat sie mir tatsächlich geholfen, meinen Code zum Laufen zu bringen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X