WPF单一实例的最佳实践(WPF Single Instance Best Practices)

2019-07-18 15:55发布

这是我工作迄今已创建一个实例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)有人问我代码分析实现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

Answer 1:

1)它看起来像一个标准的Dispose实现了我。 这是不是真的有必要(见第6点),但它不会做任何伤害。 (在关闭它清理有点像燃烧下来之前打扫屋子,恕我直言,但对此事的看法有所不同。)

无论如何,为什么不使用“处置”的清理方法的名称,即使它不会直接叫什么名字? 你可以把它称为“清理”,但请记住您也可以编写代码对人类和处置看起来很熟悉,并在.NET上任何人了解什么是它。 所以,去为“处置”。

2)I一直认为m_Mutex = new Mutex(false, mutexName); 我认为这更是一个惯例,技术优势,但是。

3)从MSDN:

如果该消息被成功登记,则返回值是到0xFFFF范围内的一个0xC000时消息标识符。

所以,我不担心。 通常情况下,这个类的功能,UINT不用于“它不适合诠释,让我们使用UINT所以我们有更多的东西”,但澄清合同“函数永远不会返回负值”。

4)如果我想避免调用它,你会停机,同样的理由#1

5)有一对夫妇的做这件事的方式。 在Win32中最简单的方法就是拥有第二个实例进行调用SetForegroundWindow(看看这里: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx ); 但是,我不知道是否有一个相当于WPF功能,或者,如果你需要的PInvoke它。

6)

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

这是确定的:当一个进程终止时,该进程拥有的所有句柄被释放; 互斥体被释放为好。

总之,我的建议:

  • 我将根据命名的同步对象使用的方法:它是更windows平台(一个或多个)上建立。 (考虑多用户系统时,像终端服务器小心!命名同步对象的组合,也许,用户名/ SID和应用程序名)
  • 使用Windows API来提高先前的实例(请参阅我在#5点链接),或WPF等价物。
  • 你可能不必担心崩溃(内核将减少对你的内核对象裁判计数器;做一个小测试反正),但如果我可以建议的改进:如果你的第一个应用程序实例不会崩溃的话,但挂起? (与Firefox会发生。我敢肯定,这发生在你身上呢!无窗,FF过程中,你不能打开一个新的)。 在这种情况下,它可能是好的,如果应用程序/窗口响应的另一种技术或两者结合起来,以一个)测试; B)找到挂起实例,并终止它

例如,你可以使用你的技术(尝试发送/发布消息的窗口 - 如果不回嘴它卡),加上MSK技术,发现并终止旧进程。 然后正常启动。



Answer 2:

有几种选择,

  • 互斥
  • 进程管理器
  • 命名信号量
  • 使用监听套接字

    互斥

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

    进程管理器

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

使用监听套接字

通知另一个应用程序的方法是打开一个TCP连接它。 创建一个套接字,绑定到一个端口,并听取在后台线程的连接。 如果成功,运行正常。 如果没有,请到该端口的连接,这标志着另一个实例第二个应用程序启动已尝试。 那么原来的实例可以把它的主窗口前面,如果合适的话。

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

在Win32沿单实例应用程序C#.NET



Answer 3:

对于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);
    }          
}


Answer 4:

我想有一个好一点的用户体验 - 如果另一个实例已在运行,让我们激活它,而不是显示二审错误。 下面是我的实现。

我用命名的互斥体为确保只有一个实例在运行并命名的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
}

而在BringToForeground 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();
    }

并添加启动= “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>

为我工作:)



Answer 5:

来处理,最简单的方法就是使用一个命名信号量。 尝试这样的事情...

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

}


Answer 6:

我用这个简单的TCP套接字(在Java中,10年前)。

  1. 在启动时连接到一个预定义的端口,如果连接被接受,另一个实例正在运行,如果没有,启动一个TCP监听器
  2. 一旦有人连接到您,弹出窗口和断开


Answer 7:

为防止第二个实例,

  • 使用的EventWaitHandle(因为我们是在谈论一个事件)
  • 使用任务,
  • 不需要互斥代码,
  • 没有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 (WaitHandleCannotBeOpenedException)
        {
            new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
        }
    }
}

第二个版本:除了上述信号的其他实例显示窗口(更改的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 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 (WaitHandleCannotBeOpenedException)
        {
            // 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();
    }
}

将该代码作为类的下降,将@ 自足-C-夏普-WPF兼容的实用程序的类 / Utils.SingleInstance.cs



Answer 8:

这是一个简单的解决方案,在这种情况下,它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程序



Answer 9:

下面是例子,带来了老实例前景藏汉:

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

但是,如果你的主窗口标题不会改变durig运行时,它才有效。

编辑:

您还可以使用Startup事件App.xaml ,而不是覆盖的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)在这种情况下!



文章来源: WPF Single Instance Best Practices