什么是创建单一的正确方法

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

我知道这与一些称为互斥体的神秘事物有关,我很少能找到一个能够阻止并解释其中之一的人。

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


这是一篇关于互斥解决方案的非常好的文章。 由于两个原因,文章描述的方法是有利的。

首先,它不需要依赖于Microsoft.VisualBasic程序集。 如果我的项目已经依赖于该程序集,我可能会主张使用接受答案中显示的方法。 但事实上,我没有使用Microsoft.VisualBasic程序集,我宁愿不添加不必要的依赖项到我的项目中。

其次,文章展示了当用户尝试启动另一个实例时,如何将应用程序的现有实例置于前台。 这是一个非常好的触摸,这里描述的其他Mutex解决方案没有解决。


UPDATE

截至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;
        }
    }
    

    你可以使用Mutex类,但你很快就会发现你将需要实现代码来自己传递参数等。 那么,当我阅读Chris Sell的书时,我在WinForms编程时学到了一招。 这个技巧使用框架中已经可用的逻辑。 我不了解你,但是当我了解可以在框架中重复使用的东西时,通常我会采用这条路线,而不是重新发明轮子。 当然,除非我不想做任何事。

    当我进入WPF时,我想出了一种使用相同代码的方式,但在WPF应用程序中。 这个解决方案应该基于你的问题来满足你的需求。

    首先,我们需要创建我们的应用程序类。 在这个类中,我们将覆盖OnStartup事件并创建一个名为Activate的方法,稍后将使用它。

    public class SingleInstanceApplication : System.Windows.Application
    {
        protected override void OnStartup(System.Windows.StartupEventArgs e)
        {
            // Call the OnStartup event on our base class
            base.OnStartup(e);
    
            // Create our MainWindow and show it
            MainWindow window = new MainWindow();
            window.Show();
        }
    
        public void Activate()
        {
            // Reactivate the main window
            MainWindow.Activate();
        }
    }
    

    其次,我们需要创建一个可以管理我们实例的类。 在我们了解之前,我们实际上将重用Microsoft.VisualBasic程序集中的一些代码。 因为我在这个例子中使用了C#,所以我不得不引用这个程序集。 如果你使用VB.NET,你不需要做任何事情。 我们要使用的类是WindowsFormsApplicationBase,并继承我们的实例管理器,然后利用属性和事件来处理单个实例。

    public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
    {
        private SingleInstanceApplication _application;
        private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;
    
        public SingleInstanceManager()
        {
            IsSingleInstance = true;
        }
    
        protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
        {
            // First time _application is launched
            _commandLine = eventArgs.CommandLine;
            _application = new SingleInstanceApplication();
            _application.Run();
            return false;
        }
    
        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
        {
            // Subsequent launches
            base.OnStartupNextInstance(eventArgs);
            _commandLine = eventArgs.CommandLine;
            _application.Activate();
        }
    }
    

    基本上,我们使用VB位来检测单个实例并相应地进行处理。 OnStartup将在第一次加载时触发。 当应用程序再次运行时,会触发OnStartupNextInstance。 正如你所看到的,我可以通过事件参数来了解通过命令行传递的内容。 我将该值设置为实例字段。 您可以在这里解析命令行,或者您可以通过构造函数和对Activate方法的调用将它传递给应用程序。

    第三,现在是创建我们的入口点的时候了。 我们将利用SingleInstanceManager,而不是像通常那样新增应用程序。

    public class EntryPoint
    {
        [STAThread]
        public static void Main(string[] args)
        {
            SingleInstanceManager manager = new SingleInstanceManager();
            manager.Run(args);
        }
    }
    

    那么,我希望你能够遵循一切,并能够使用这个实现并使其成为你自己的。


    从这里。

    跨进程互斥的常见用途是确保一次只能运行一个程序的实例。 这是如何完成的:

    class OneAtATimePlease {
    
      // Use a name unique to the application (eg include your company URL)
      static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");
    
      static void Main()
      {
        // Wait 5 seconds if contended – in case another instance
        // of the program is in the process of shutting down.
        if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
        {
            Console.WriteLine("Another instance of the app is running. Bye!");
            return;
        }
    
        try
        {    
            Console.WriteLine("Running - press Enter to exit");
            Console.ReadLine();
        }
        finally
        {
            mutex.ReleaseMutex();
        }    
      }    
    }
    

    Mutex的一个很好的特性是,如果应用程序在没有释放ReleaseMutex的情况下终止,CLR将自动释放Mutex。

    链接地址: http://www.djcxy.com/p/1451.html

    上一篇: What is the correct way to create a single

    下一篇: Can you explain closures (as they relate to Python)?