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

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

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


当前回答

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

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

其他回答

MSDN实际上有一个c#和VB的示例应用程序可以做到这一点:http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

The most common and reliable technique for developing single-instance detection is to use the Microsoft .NET Framework remoting infrastructure (System.Remoting). The Microsoft .NET Framework (version 2.0) includes a type, WindowsFormsApplicationBase, which encapsulates the required remoting functionality. To incorporate this type into a WPF application, a type needs to derive from it, and be used as a shim between the application static entry point method, Main, and the WPF application's Application type. The shim detects when an application is first launched, and when subsequent launches are attempted, and yields control the WPF Application type to determine how to process the launches.

For C# people just take a deep breath and forget about the whole 'I don't wanna include VisualBasic DLL'. Because of this and what Scott Hanselman says and the fact that this pretty much is the cleanest solution to the problem and is designed by people who know a lot more about the framework than you do. From a usability standpoint the fact is if your user is loading an application and it is already open and you're giving them an error message like 'Another instance of the app is running. Bye' then they're not gonna be a very happy user. You simply MUST (in a GUI application) switch to that application and pass in the arguments provided - or if command line parameters have no meaning then you must pop up the application which may have been minimized.

这个框架已经支持这个功能了——只是有些白痴把DLL命名为Microsoft罢了。VisualBasic,它没有被放到微软。ApplicationUtils之类的。克服它——或者打开Reflector。

提示:如果你完全按原样使用这种方法,并且你已经有了一个带有资源等的App.xaml,你也会想要看看这个。

好吧,我有一个一次性的类,对于大多数用例来说很容易:

像这样使用它:

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

下面就是:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

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

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}

一个节省时间的c# Winforms解决方案…

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}

简单地使用一个StreamWriter,怎么样?

System.IO.File.StreamWriter OpenFlag = null;   //globally

and

try
{
    OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
    Environment.Exit(0);
}

我最喜欢的解决方案来自MVP丹尼尔·沃恩: 强制执行单实例Wpf应用程序

它使用MemoryMappedFile将命令行参数发送给第一个实例:

/// <summary>
/// This class allows restricting the number of executables in execution, to one.
/// </summary>
public sealed class SingletonApplicationEnforcer
{
    readonly Action<IEnumerable<string>> processArgsFunc;
    readonly string applicationId;
    Thread thread;
    string argDelimiter = "_;;_";

    /// <summary>
    /// Gets or sets the string that is used to join 
    /// the string array of arguments in memory.
    /// </summary>
    /// <value>The arg delimeter.</value>
    public string ArgDelimeter
    {
        get
        {
            return argDelimiter;
        }
        set
        {
            argDelimiter = value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SingletonApplicationEnforcer"/> class.
    /// </summary>
    /// <param name="processArgsFunc">A handler for processing command line args 
    /// when they are received from another application instance.</param>
    /// <param name="applicationId">The application id used 
    /// for naming the <seealso cref="EventWaitHandle"/>.</param>
    public SingletonApplicationEnforcer(Action<IEnumerable<string>> processArgsFunc, 
        string applicationId = "DisciplesRock")
    {
        if (processArgsFunc == null)
        {
            throw new ArgumentNullException("processArgsFunc");
        }
        this.processArgsFunc = processArgsFunc;
        this.applicationId = applicationId;
    }

    /// <summary>
    /// Determines if this application instance is not the singleton instance.
    /// If this application is not the singleton, then it should exit.
    /// </summary>
    /// <returns><c>true</c> if the application should shutdown, 
    /// otherwise <c>false</c>.</returns>
    public bool ShouldApplicationExit()
    {
        bool createdNew;
        string argsWaitHandleName = "ArgsWaitHandle_" + applicationId;
        string memoryFileName = "ArgFile_" + applicationId;

        EventWaitHandle argsWaitHandle = new EventWaitHandle(
            false, EventResetMode.AutoReset, argsWaitHandleName, out createdNew);

        GC.KeepAlive(argsWaitHandle);

        if (createdNew)
        {
            /* This is the main, or singleton application. 
                * A thread is created to service the MemoryMappedFile. 
                * We repeatedly examine this file each time the argsWaitHandle 
                * is Set by a non-singleton application instance. */
            thread = new Thread(() =>
                {
                    try
                    {
                        using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(memoryFileName, 10000))
                        {
                            while (true)
                            {
                                argsWaitHandle.WaitOne();
                                using (MemoryMappedViewStream stream = file.CreateViewStream())
                                {
                                    var reader = new BinaryReader(stream);
                                    string args;
                                    try
                                    {
                                        args = reader.ReadString();
                                    }
                                    catch (Exception ex)
                                    {
                                        Debug.WriteLine("Unable to retrieve string. " + ex);
                                        continue;
                                    }
                                    string[] argsSplit = args.Split(new string[] { argDelimiter }, 
                                                                    StringSplitOptions.RemoveEmptyEntries);
                                    processArgsFunc(argsSplit);
                                }

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Unable to monitor memory file. " + ex);
                    }
                });

            thread.IsBackground = true;
            thread.Start();
        }
        else
        {
            /* Non singleton application instance. 
                * Should exit, after passing command line args to singleton process, 
                * via the MemoryMappedFile. */
            using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(memoryFileName))
            {
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    var writer = new BinaryWriter(stream);
                    string[] args = Environment.GetCommandLineArgs();
                    string joined = string.Join(argDelimiter, args);
                    writer.Write(joined);
                }
            }
            argsWaitHandle.Set();
        }

        return !createdNew;
    }
}