WPF Single Instance Best Practices

前端 未结 11 1528
名媛妹妹
名媛妹妹 2020-12-07 08:19

This is the code I implemented so far to create a single instance WPF application:

#region Using Directives
using System;
using System.Globalization;
using S         


        
相关标签:
11条回答
  • 2020-12-07 09:20

    There are Several choices,

    • Mutex
    • Process manager
    • Named Semaphore
    • Use a listener socket

    Mutex

    Mutex myMutex ;
    
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        bool aIsNewInstance = false;
        myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);  
        if (!aIsNewInstance)
        {
            MessageBox.Show("Already an instance is running...");
            App.Current.Shutdown();  
        }
    }
    

    Process manager

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        Process proc = Process.GetCurrentProcess();
        int count = Process.GetProcesses().Where(p=> 
            p.ProcessName == proc.ProcessName).Count();
    
        if (count > 1)
        {
            MessageBox.Show("Already an instance is running...");
            App.Current.Shutdown(); 
        }
    }
    

    Use a listener socket

    One way to signal another application is to open a Tcp connection to it. Create a socket, bind to a port, and listen on a background thread for connections. If this succeeds, run normally. If not, make a connection to that port, which signals the other instance that a second application launch attempt has been made. The original instance can then bring its main window to the front, if appropriate.

    “Security” software / firewalls might be an issue.

    Single Instance Application C#.Net along with Win32

    0 讨论(0)
  • 2020-12-07 09:20

    For WPF just use:

    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();
            }
    
            base.OnStartup(e);
        }          
    }
    
    0 讨论(0)
  • 2020-12-07 09:22

    The most straight forward way to handle that would be using a named semaphore. Try something like this...

    public partial class App : Application
    {
        Semaphore sema;
        bool shouldRelease = false;
    
        protected override void OnStartup(StartupEventArgs e)
        {
    
            bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);
    
            if (result) // we have another instance running
            {
                App.Current.Shutdown();
            }
            else
            {
                try
                {
                    sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
                }
                catch
                {
                    App.Current.Shutdown(); //
                }
            }
    
            if (!sema.WaitOne(0))
            {
                App.Current.Shutdown();
            }
            else
            {
                shouldRelease = true;
            }
    
    
            base.OnStartup(e);
        }
    
        protected override void OnExit(ExitEventArgs e)
        {
            if (sema != null && shouldRelease)
            {
                sema.Release();
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-07 09:26

    I wanted to have a bit better user experience - if another instance is already running let's activate it rather than showing an error about the second instance. Here is my implementation.

    I use named Mutex for making sure that only one instance is running and named EventWaitHandle to pass notification from one instance to another.

    App.xaml.cs:

    /// <summary>Interaction logic for App.xaml</summary>
    public partial class App
    {
        #region Constants and Fields
    
        /// <summary>The event mutex name.</summary>
        private const string UniqueEventName = "{GUID}";
    
        /// <summary>The unique mutex name.</summary>
        private const string UniqueMutexName = "{GUID}";
    
        /// <summary>The event wait handle.</summary>
        private EventWaitHandle eventWaitHandle;
    
        /// <summary>The mutex.</summary>
        private Mutex mutex;
    
        #endregion
    
        #region Methods
    
        /// <summary>The app on startup.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void AppOnStartup(object sender, StartupEventArgs e)
        {
            bool isOwned;
            this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
            this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
    
            // So, R# would not give a warning that this variable is not used.
            GC.KeepAlive(this.mutex);
    
            if (isOwned)
            {
                // Spawn a thread which will be waiting for our event
                var thread = new Thread(
                    () =>
                    {
                        while (this.eventWaitHandle.WaitOne())
                        {
                            Current.Dispatcher.BeginInvoke(
                                (Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
                        }
                    });
    
                // It is important mark it as background otherwise it will prevent app from exiting.
                thread.IsBackground = true;
    
                thread.Start();
                return;
            }
    
            // Notify other instance so it could bring itself to foreground.
            this.eventWaitHandle.Set();
    
            // Terminate this instance.
            this.Shutdown();
        }
    
        #endregion
    }
    

    And BringToForeground in MainWindow.cs:

        /// <summary>Brings main window to foreground.</summary>
        public void BringToForeground()
        {
            if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
            {
                this.Show();
                this.WindowState = WindowState.Normal;
            }
    
            // According to some sources these steps gurantee that an app will be brought to foreground.
            this.Activate();
            this.Topmost = true;
            this.Topmost = false;
            this.Focus();
        }
    

    And add Startup="AppOnStartup" (thanks vhanla!):

    <Application x:Class="MyClass.App"  
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 Startup="AppOnStartup">
        <Application.Resources>
        </Application.Resources>
    </Application>
    

    Works for me :)

    0 讨论(0)
  • 2020-12-07 09:26

    to prevent a second instance (and signal the existing),

    • using EventWaitHandle (since we are talking about an event),
    • using Task,
    • no Mutex code required,
    • no TCP,
    • no Pinvokes,
    • no GarbageCollection stuff,
    • thread save
    • simple

    it could be done like this (this for an WPF app (see ref to App()), but works on WinForms as well):

    public partial class App : Application
    {
        public App()
        {
            // initiate it. Call it first.
            preventSecond();
        }
    
        private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
    
        private void preventSecond()
        {
            try
            {
                EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
                this.Shutdown();
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
            }
        }
    }
    

    Second version: above plus signaling the other instance to show the window (change the MainWindow part for WinForms):

    public partial class App : Application
    {
        public App()
        {
            // initiate it. Call it first.
            //preventSecond();
            SingleInstanceWatcher();
        }
    
        private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
        private EventWaitHandle eventWaitHandle;
    
        /// <summary>prevent a second instance and signal it to bring its mainwindow to foreground</summary>
        /// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
        private void SingleInstanceWatcher()
        {
            // check if it is already open.
            try
            {
                // try to open it - if another instance is running, it will exist , if not it will throw
                this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
    
                // Notify other instance so it could bring itself to foreground.
                this.eventWaitHandle.Set();
    
                // Terminate this instance.
                this.Shutdown();
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                // listen to a new event (this app instance will be the new "master")
                this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
            }
    
            // if this instance gets the signal to show the main window
            new Task(() =>
            {
                while (this.eventWaitHandle.WaitOne())
                {
                    Current.Dispatcher.BeginInvoke((Action)(() =>
                    {
                        // could be set or removed anytime
                        if (!Current.MainWindow.Equals(null))
                        {
                            var mw = Current.MainWindow;
    
                            if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
                            {
                                mw.Show();
                                mw.WindowState = WindowState.Normal;
                            }
    
                            // According to some sources these steps are required to be sure it went to foreground.
                            mw.Activate();
                            mw.Topmost = true;
                            mw.Topmost = false;
                            mw.Focus();
                        }
                    }));
                }
            })
            .Start();
        }
    }
    

    This code as a drop in class, will be @ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.cs

    0 讨论(0)
提交回复
热议问题