一尘不染

创建单实例WPF应用程序的正确方法是什么?

c#

在.NET(而不是Windows
Forms
或控制台)下使用C#和WPF
,创建只能作为单个实例运行的应用程序的正确方法是什么?

我知道它与某种称为互斥量的神话事物有关,我很少能找到一个烦人的人来阻止并解释其中的一个。

该代码还需要告知已经运行的实例用户试图启动第二个实例,并且还可能传递任何命令行参数(如果存在)。


阅读 472

收藏
2020-05-19

共1个答案

一尘不染

这是有关Mutex解决方案的非常好的文章。该文章描述的方法有两个方面的优势。

首先,它不需要依赖于Microsoft.VisualBasic程序集。如果我的项目已经依赖于该程序集,那么我可能会提倡使用另一个答案中所示的方法。但实际上,我不使用Microsoft.VisualBasic程序集,而我不想在项目中添加不必要的依赖项。

其次,本文介绍了当用户尝试启动另一个实例时如何将应用程序的现有实例置于前台。这是这里描述的其他Mutex解决方案无法解决的一个很好的方面。


更新

截至2014年8月1日,我上面链接的文章仍处于活动状态,但该博客已有一段时间没有更新。这使我担心,最终它可能会消失,并且随之而来的是所倡导的解决方案。我在此转载本文的内容,以供后代参考。这些单词仅属于Sanity
Free Coding
的博客所有者。

今天,我想重构一些禁止我的应用程序运行其自身多个实例的代码。

以前,我曾使用System.Diagnostics.Process在进程列表中搜索myapp.exe的实例。虽然这样做有效,但会带来很多开销,我想要更清洁的东西。

知道我可以为此使用互斥锁(但以前从未做过),因此着手缩减代码并简化生活。

在我的应用程序主类中,我创建了一个名为Mutex的静态变量

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

拥有一个已命名的互斥锁可以使我们在多个线程和进程之间进行堆栈同步,这正是我要寻找的魔术。

Mutex.WaitOne有一个重载,它指定了我们等待的时间。由于我们实际上并不想同步代码(更多的是检查当前是否在使用它),我们将重载与两个参数一起使用:Mutex.WaitOne(Timespan
timeout,bool exitContext)
。如果可以输入,则等待返回true,否则返回false。在这种情况下,我们根本不想等待;如果正在使用我们的互斥锁,请跳过它并继续前进,以便我们传入TimeSpan.Zero(等待0毫秒),并将exitContext设置为true,以便在尝试获取对其的锁定之前可以退出同步上下文。使用此代码,我们将Application.Run代码包装在如下代码中:

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

因此,如果我们的应用程序正在运行,WaitOne将返回false,我们将收到一个消息框。

我没有显示消息框,而是选择使用一个小的Win32来通知正在运行的实例有人忘记了它已经在运行(通过将自身置于所有其他窗口的顶部)。为此,我使用PostMessage将自定义消息广播到每个窗口(该自定义消息
已由运行的应用程序注册到RegisterWindowMessage中,这意味着只有我的应用程序知道它是什么),然后退出第二个实例。正在运行的应用程序实例将接收该通知并对其进行处理。为了做到这一点,我以主要形式覆盖了WndProc,并收听了自定义通知。当我收到该通知时,我将表单的TopMost属性设置为true,以将其置于顶部。

我最终得到的是:

  • Program.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(正面部分)
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;
    }
}
2020-05-19