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

我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。

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


当前回答

[我在下面提供了控制台和wpf应用程序的示例代码。]

在创建命名的Mutex实例后,只需检查createdNew变量的值(示例如下!)。

布尔值createdNew将返回false:

如果命名为“YourApplicationNameHere”的互斥锁实例已经存在 在系统某处创建

布尔值createdNew将返回true:

如果这是第一个名为“YourApplicationNameHere”的互斥锁 系统。

控制台应用程序-示例:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WPF-Example:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}

其他回答

这是通过事件实现的相同的事情。

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}

使用互斥量解决方案:

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}

看起来有一个很好的方法来处理这个问题:

WPF单实例应用程序

这提供了一个类,您可以添加它来管理所有互斥量和消息传递的cruff,从而将实现简化到非常简单的程度。

更新2017-01-25。在尝试了一些东西之后,我决定使用VisualBasic.dll,它更容易,工作效果更好(至少对我来说)。我让我之前的答案只是作为参考…

只是作为参考,这是我如何不传递参数(我找不到任何理由这样做…我指的是带有参数的单个应用程序,这些参数可以从一个实例传递到另一个实例)。 如果需要文件关联,那么应用程序应该(根据用户的标准期望)为每个文档实例化。如果你必须传递args到现有的应用程序,我想我会使用vb dll。

不传递参数(只是单实例应用程序),我更喜欢不注册一个新的窗口消息,不覆盖Matt Davis解决方案中定义的消息循环。虽然添加一个VisualBasic dll不是一个大问题,但我不喜欢添加一个新的引用只是做单个实例应用程序。此外,我更喜欢用Main实例化一个新类,而不是调用Shutdown from app. startup重写以确保尽快退出。

希望大家都喜欢……或者会启发一点:-)

项目启动类应该设置为“SingleInstanceApp”。

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

我在这里找不到一个简单的解决方案,所以我希望有人会喜欢这个:

更新2018-09-20

把这段代码放在Program.cs中:

using System.Diagnostics;

static void Main()
{
    Process thisProcess = Process.GetCurrentProcess();
    Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
    if (allProcesses.Length > 1)
    {
        // Don't put a MessageBox in here because the user could spam this MessageBox.
        return;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}