首页 文章

WPF单实例最佳实践

提问于
浏览
31

这是我到目前为止实现的代码,用于创建单个实例WPF应用程序:

#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion

namespace MyWPF
{
    public partial class MainApplication : Application, IDisposable
    {
        #region Members
        private Int32 m_Message;
        private Mutex m_Mutex;
        #endregion

        #region Methods: Functions
        private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
        {
            if (message == m_Message)
            {
                if (MainWindow.WindowState == WindowState.Minimized)
                    MainWindow.WindowState = WindowState.Normal;

                Boolean topmost = MainWindow.Topmost;

                MainWindow.Topmost = true;
                MainWindow.Topmost = topmost;
            }

            return IntPtr.Zero;
        }

        private void Dispose(Boolean disposing)
        {
            if (disposing && (m_Mutex != null))
            {
                m_Mutex.ReleaseMutex();
                m_Mutex.Close();
                m_Mutex = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Methods: Overrides
        protected override void OnStartup(StartupEventArgs e)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Boolean mutexCreated;
            String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name);

            m_Mutex = new Mutex(true, mutexName, out mutexCreated);
            m_Message = NativeMethods.RegisterWindowMessage(mutexName);

            if (!mutexCreated)
            {
                m_Mutex = null;

                NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);

                Current.Shutdown();

                return;
            }

            base.OnStartup(e);

            MainWindow window = new MainWindow();
            MainWindow = window;
            window.Show(); 

            HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
        }

        protected override void OnExit(ExitEventArgs e)
        {
            Dispose();
            base.OnExit(e);
        }
        #endregion
    }
}

一切都很完美......但我对它有一些疑问,我想收到你关于如何改进我的方法的建议 .

1)Code Analysis要求我实现 IDisposable 接口,因为我使用的是 IDisposable 成员( Mutex ) . 我的 Dispose() 实施是否足够好?我应该避免它,因为它永远不会被调用吗?

2)最好使用 m_Mutex = new Mutex(true, mutexName, out mutexCreated); 并检查结果或使用 m_Mutex = new Mutex(false, mutexName); ,然后检查 m_Mutex.WaitOne(TimeSpan.Zero, false); ?在多线程的情况下,我的意思是......

3) RegisterWindowMessage API调用应返回 UInt32 ...但 HwndSourceHook 仅接受 Int32 作为消息值...我是否应该担心意外行为(比如大于 Int32.MaxValue 的结果)?

4)在 OnStartup 覆盖中...我应该执行 base.OnStartup(e); ,即使另一个实例已在运行并且我要关闭应用程序?

5)是否有更好的方法将现有实例置于不需要设置 Topmost 值的顶部?也许 Activate()

6)你能看到我的方法有什么缺陷吗?关于多线程,坏的异常处理和类似的东西?例如......如果我的应用程序在 OnStartupOnExit 之间崩溃会发生什么?

9 回答

  • 1

    1)它看起来像我的标准Dispose实现 . 这不是必要的(见第6点),但它没有任何伤害 . (清理关闭它有点像清理房子,然后烧掉它,恕我直言,但对此事的意见不同......)

    无论如何,为什么不使用“Dispose”作为清理方法的名称,即使它没有被直接调用?您可以将其称为“清理”,但请记住您也为人类编写代码,Dispose看起来很熟悉,.NET上的任何人都了解它的用途 . 所以,去“Dispose” .

    2)我一直看到 m_Mutex = new Mutex(false, mutexName); 我认为这更像是一种技术优势的惯例 .

    3)来自MSDN:

    如果消息成功注册,则返回值是0xC000到0xFFFF范围内的消息标识符 .

    所以我不担心 . 通常,对于这类函数,UInt不用于“它不适合Int,让我们使用UInt所以我们有更多东西”但澄清 Contract “函数永远不会返回负值” .

    4)如果你关机,我会避免打电话,原因与#1相同

    5)有几种方法可以做到这一点 . Win32中最简单的方法就是让第二个实例调用SetForegroundWindow(看这里:http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx);但是,我不知道是否有等效的WPF功能,或者你是否需要PInvoke它 .

    6)

    例如......如果我的应用程序在OnStartup和OnExit之间崩溃会发生什么?

    没关系:当进程终止时,进程拥有的所有句柄都被释放;互斥体也被释放了 .

    总之,我的建议:

    • 我会使用一种基于命名同步对象的方法:它是在Windows平台上 Build 的 . (在考虑多用户系统时要小心,比如终端服务器!将同步对象命名为,可能是用户名/ SID和应用程序名称的组合)

    • 使用Windows API引发上一个实例(请参阅我在第5点的链接)或WPF等效项 .

    • 你可能不必担心崩溃(内核会为你减少内核对象的ref计数器;无论如何都做一点测试),但是如果我可以建议一个改进:如果你的第一个应用程序实例没有崩溃但是挂起怎么办? ? (发生在Firefox上......我确定它也发生在你身上!没有窗口,ff进程,你无法打开一个新的) . 在这种情况下,将另一种技术或两种技术结合起来可能是好的,a)测试应用程序/窗口是否响应; b)找到挂起的实例并终止它

    例如,您可以使用您的技术(尝试向窗口发送/发送消息 - 如果没有回复它被卡住),加上MSK技术,以查找和终止旧进程 . 然后正常开始 .

  • 5

    有几种选择,

    • Mutex

    • 流程经理

    • 命名为信号量

    • 使用侦听器套接字

    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

    向另一个应用程序发出信号的一种方法是打开一个Tcp连接 . 创建套接字,绑定到端口,然后在后台线程上侦听连接 . 如果成功,请正常运行 . 如果没有, Build 与该端口的连接,这标志着另一个实例已经进行了第二次应用程序启动尝试 . 如果合适,原始实例可以将其主窗口置于前面 .

    “安全”软件/防火墙可能是一个问题 .

    Single Instance Application C#.Net along with Win32

  • 4

    对于WPF,只需使用:

    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);
        }          
    }
    
  • 25

    我希望有一个更好的用户体验 - 如果另一个实例已经在运行,让我们激活它,而不是显示有关第二个实例的错误 . 这是我的实施 .

    我使用命名的Mutex来确保只有一个实例正在运行并命名为EventWaitHandle以将通知从一个实例传递到另一个实例 .

    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
    }
    

    和MainWindow.cs中的BringToForeground:

    /// <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();
        }
    

    并添加Startup =“AppOnStartup”(谢谢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>
    

    适合我:)

  • 2

    处理它的最直接的方法是使用命名信号量 . 尝试这样的事......

    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

    我已经使用了一个简单的TCP套接字(在Java中,10年前) .

    • 启动时连接到预定义端口,如果连接被接受,则另一个实例正在运行,如果没有,则启动TCP侦听器

    • 有人连接到你后,弹出窗口并断开连接

  • 26

    这是一个简单的解决方案,打开您的启动文件(从应用程序启动的位置查看),在本例中为MainWindow.xaml . 打开MainWindow.xaml.cs文件 . 转到构造函数,并在intializecomponent()之后添加以下代码:

    Process Currentproc = Process.GetCurrentProcess();
    
    Process[] procByName=Process.GetProcessesByName("notepad");  //Write the name of your exe file in inverted commas
    if(procByName.Length>1)
    {
      MessageBox.Show("Application is already running");
      App.Current.Shutdown();
     }
    

    不要忘记添加System.Diagnostics

  • 39

    防止第二个实例,不需要Mutex代码,使用Task,没有TCP,没有Pinvokes,线程保存 - 它可以这样做(这对于WPF应用程序(请参阅参考App()),但也适用于WinForms ):

    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
            {
                new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
            }
        }
    }
    

    第二个版本:上面加上信号通知另一个实例来显示窗口(更改WinForms的MainWindow部分):

    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 foregorund</summary>
        /// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
        private void SingleInstanceWatcher()
        {
            // check if it is allready open.
            try
            {
                // try to open it - if another instance is running, it will exist
                this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
    
                // Notify other instance so it could bring itself to foreground.
                this.eventWaitHandle.Set();
    
                // Terminate this instance.
                this.Shutdown();
            }
            catch
            {
                // listen to a new event
                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 gurantee that an app will be brought to foreground.
                            mw.Activate();
                            mw.Topmost = true;
                            mw.Topmost = false;
                            mw.Focus();
                        }
                    }));
                }
            })
            .Start();
        }
    }
    

    这段代码作为一个下降类,将是@ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.cs

  • 7

    这是将旧实例带到前台的示例:

    public partial class App : Application
    {
        [DllImport("user32", CharSet = CharSet.Unicode)]
        static extern IntPtr FindWindow(string cls, string win);
        [DllImport("user32")]
        static extern IntPtr SetForegroundWindow(IntPtr hWnd);
        [DllImport("user32")]
        static extern bool IsIconic(IntPtr hWnd);
        [DllImport("user32")]
        static extern bool OpenIcon(IntPtr hWnd);
    
        private static Mutex _mutex = null;
    
        protected override void OnStartup(StartupEventArgs e)
        {
            const string appName = "LinkManager";
            bool createdNew;
    
            _mutex = new Mutex(true, appName, out createdNew);
    
            if (!createdNew)
            {
                ActivateOtherWindow();
                //app is already running! Exiting the application  
                Application.Current.Shutdown();
            }
    
            base.OnStartup(e);
        }
    
        private static void ActivateOtherWindow()
        {
            var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
            if (other != IntPtr.Zero)
            {
                SetForegroundWindow(other);
                if (IsIconic(other))
                    OpenIcon(other);
            }
        }
    }
    

    但它只有在你的主窗口 Headers 不改变durig运行时才会起作用 .

    Edit:

    您也可以在 App.xaml 中使用 Startup 事件,而不是覆盖 OnStartup .

    // App.xaml.cs
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        const string appName = "LinkManager";
        bool createdNew;
    
        _mutex = new Mutex(true, appName, out createdNew);
    
        if (!createdNew)
        {
            ActivateOtherWindow();
            //app is already running! Exiting the application  
            Application.Current.Shutdown();
        }
    }
    
    // App.xaml
    <Application x:Class="MyApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyApp"
             StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event
    

    在这种情况下,请记住不要拨打 base.OnStartup(e)

相关问题