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.

8voto

Dan Punkte 9439

Der folgende Code ist meine WCF Named Pipes-Lösung zur Registrierung einer Einzelinstanzanwendung. Es ist schön, weil es auch ein Ereignis auslöst, wenn eine andere Instanz versucht, zu starten, und die Befehlszeile der anderen Instanz erhält.

Es ist auf WPF ausgerichtet, da es die System.Windows.StartupEventHandler Klasse, aber das kann leicht geändert werden.

Dieser Code erfordert einen Verweis auf PresentationFramework und System.ServiceModel .

Verwendung:

class Program
{
    static void Main()
    {
        var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");

        if (SingleInstanceManager.VerifySingleInstance(applicationId))
        {
            SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;

            // Start the application
        }
    }

    static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
    {
        // Do something in response to another instance starting up.
    }
}

Quellcode:

/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
  /// <summary>
  /// Raised when another instance attempts to start up.
  /// </summary>
  public static event StartupEventHandler OtherInstanceStarted;

  /// <summary>
  /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
  /// send the main instance this instance's startup information.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if this instance is the main instance.</returns>
  public static bool VerifySingleInstace(Guid guid)
  {
    if (!AttemptPublishService(guid))
    {
      NotifyMainInstance(guid);

      return false;
    }

    return true;
  }

  /// <summary>
  /// Attempts to publish the service.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if the service was published successfully.</returns>
  private static bool AttemptPublishService(Guid guid)
  {
    try
    {
      ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
      NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
      serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
      serviceHost.Open();

      return true;
    }
    catch
    {
      return false;
    }
  }

  /// <summary>
  /// Notifies the main instance that this instance is attempting to start up.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  private static void NotifyMainInstance(Guid guid)
  {
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
    {
      ISingleInstance singleInstance = factory.CreateChannel();
      singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
    }
  }

  /// <summary>
  /// Creates an address to publish/contact the service at based on a globally unique identifier.
  /// </summary>
  /// <param name="guid">The identifier for the application.</param>
  /// <returns>The address to publish/contact the service.</returns>
  private static string CreateAddress(Guid guid)
  {
    return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
  }

  /// <summary>
  /// The interface that describes the single instance service.
  /// </summary>
  [ServiceContract]
  private interface ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    [OperationContract]
    void NotifyMainInstance(string[] args);
  }

  /// <summary>
  /// The implementation of the single instance service interface.
  /// </summary>
  private class SingleInstance : ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public void NotifyMainInstance(string[] args)
    {
      if (OtherInstanceStarted != null)
      {
        Type type = typeof(StartupEventArgs);
        ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
        StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
        FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
        Debug.Assert(argsField != null);
        argsField.SetValue(e, args);

        OtherInstanceStarted(null, e);
      }
    }
  }
}

6voto

Peter Punkte 61

So viele Antworten auf eine so scheinbar einfache Frage. Nur um die Dinge ein wenig aufzurütteln, hier ist meine Lösung für dieses Problem.

Die Erstellung eines Mutex kann problematisch sein, weil der JIT-Programmierer sieht, dass Sie ihn nur für einen kleinen Teil Ihres Codes verwenden und ihn als bereit für die Garbage Collection markieren wollen. Er will Sie sozusagen überlisten, weil er denkt, dass Sie den Mutex nicht so lange verwenden werden. In Wirklichkeit wollen Sie diese Mutex so lange behalten, wie Ihre Anwendung läuft. Der beste Weg, dem Garbage Collector mitzuteilen, dass er Ihre Mutex in Ruhe lassen soll, ist, sie über die verschiedenen Generationen der Garbage Collection hinweg am Leben zu erhalten. Beispiel:

var m = new Mutex(...);
...
GC.KeepAlive(m);

Ich habe die Idee von dieser Seite übernommen: http://www.ai.uga.edu/~mc/EinzelneInstanz.html

4 Stimmen

Wäre es nicht einfacher, eine gemeinsame Kopie davon in der Anwendungsklasse zu speichern?

5voto

newbieguy Punkte 638

Nicht mit Mutex aber, einfache Antwort:

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

Legen Sie es in die Program.Main() .
Beispiel :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

Sie können hinzufügen MessageBox.Show zum if -Anweisung und geben Sie "Anwendung läuft bereits" ein.
Dies könnte für jemanden hilfreich sein.

5 Stimmen

Wenn zwei Prozesse gleichzeitig gestartet werden, können sie beide zwei aktive Prozesse sehen und sich selbst beenden.

0 Stimmen

@A.T. Ja, richtig, das kann auch für Anwendungen hilfreich sein, die als Administrator ausgeführt werden, sonst

1 Stimmen

Wenn Sie eine Kopie Ihrer Anwendung erstellen und diese umbenennen, können Sie das Original und die Kopie gleichzeitig ausführen.

5voto

Joel Barsotti Punkte 2991

Es sieht so aus, als gäbe es eine wirklich gute Möglichkeit, dies zu handhaben:

WPF Einzelinstanzanwendung

Dies bietet eine Klasse, die Sie hinzufügen können, die alle Mutex und Messaging Cruff verwaltet, um Ihre Implementierung zu dem Punkt zu vereinfachen, wo es einfach trivial ist.

0 Stimmen

Als ich es ausprobierte, schien dies das vorhandene Fenster nicht in den Vordergrund zu bringen.

0 Stimmen

Diese Antwort ist ein Duplikat dieser Antwort: stackoverflow.com/a/2932076/3220898 - und beide Antworten sind jetzt nutzlos, weil der Link tot ist.

5voto

carlito Punkte 70

Sehen Sie sich den folgenden Code an. Es ist eine großartige und einfache Lösung, um mehrere Instanzen einer WPF-Anwendung zu verhindern.

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}

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