CSharp - 创建一个单实例应用程序的正确方法是什么?

  显示原文与译文双语对照的内容

下使用 C# 和 WPF. NET ( 而不是WindowsForms或者控制台),什么是正确的方法来创建一个应用程序,该应用程序只能以单个实例运行? 我知道它与一些叫做互斥的东西有关,我很少能找到一些人来停止并解释其中的一个。

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

时间:

下面是一个关于互斥解决方案的非常好的文章。 本文描述的方法有两个优点。

首先,它不需要 Microsoft.VisualBasic 程序集的依赖关系。 如果我的项目已经对该程序集有依赖关系,我可能会提倡使用在接受的答案中显示的方法。 但是,我不使用 Microsoft.VisualBasic 程序集,我宁愿不要向项目添加不必要的依赖项。

第二,本文展示了当用户尝试启动另一个实例时,如何将应用程序的现有实例引入前台。 这是一个非常好的触摸,因为这里描述的其他互斥解决方案没有地址。


更新

截至 8/1/2014,,我链接到上面的文章仍然是活跃的,但博客没有在一段时间内更新。 这让我担心,最终它可能会消失,并伴随着它,被提倡的解决方案。 我在这里为后代重新复制文章内容。 这些词只属于健全自由编码的博客所有者。

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

在这个过程中以前我有使用 System.Diagnostics.Process 来搜索我的的一个实例 虽然这样做,但它带来了大量的开销,我想要一些更干净的东西。

知道我可以为这个( 但以前从未做过) 使用一个互斥体,我开始减少代码并简化我的生活。

在我的应用程序main中,我创建了一个静态命名的互斥对象:


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

拥有一个命名的互斥允许我们在多个线程和进程之间进行堆栈同步,这就是我正在寻找的魔术。

Mutex.WaitOne 有一个重载,指定等待的时间。 由于我们实际上不希望同步代码( 更多只是检查当前是否正在使用),所以我们使用了两个参数的重载: Mutex.WaitOne(Timespan timeout, bool exitContext) 。等待一个返回真,如果它是能够进入的,假如果不是。 在这种情况下,我们根本不想等待;如果使用了互斥锁,跳过它,然后继续,这样我们就可以通过 TimeSpan.Zero ( 等待 0毫秒),并将exitContext设置为真,这样我们就可以在获取锁之前退出。 使用这里方法,我们将 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将 return false,我们将得到一个消息框。

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

下面是我的结果:

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

你可以使用互斥类,但是你很快就会发现需要实现代码来传递参数和。 嗯,我学到了一个技巧在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在应用程序重新运行时被激发。 就像你看到的,我可以通过事件参数到达 命令行 上传递的内容。 我将该值设置为一个实例字段。 你可以在这里解析 命令行,或者通过构造函数和对激活方法的调用将它传递给应用程序。

第三,是时候创建我们的切入点了。 而不是newing起应用程序像可以像平常那样,我们要充分利用我们的SingleInstanceManager 。


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

好吧,我希望你能够跟随一切,并能够使用这个实现并使它成为你自己的。

来自

cross-process互斥的一个常见用途是确保一次只能运行一个程序的实例。 如下所示:


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

互斥的一个好特性是,如果应用程序不首先被调用而被调用,CLR会自动释放互斥。

实际上,MSDN有一个用于 C# 和VB的示例应用程序,可以实现以下目的: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

开发single-instance检测最常用和可靠的技术是使用微软. NET 框架远程处理基础设施( 系统。远程处理) 。 .NET 框架( 版本 2.0 ) 包含一个类型 WindowsFormsApplicationBase,它封装所需的远程处理功能。 要将这里类型合并到WPF应用程序中,需要从它派生一个类型,并用作应用程序静态入口点方法method主应用程序和应用程序类型的WPF应用程序之间的连接。 垫片检测应用程序首次启动时的状态,以及什么时候尝试进行后续启动,并控制控制WPF应用程序类型以确定如何处理启动。

  • 对于 C# 来说,人们只需要深呼吸并忘记整个'我不希望包含VisualBasic的dll'。 由于这里 和史考特Hanselman说什么人和这一事实这几乎是最干净的解决方案,于是被设计通过了解很多关于如何比你这个框架。
  • 从可用性角度看,事实是如果你的用户正在加载一个应用程序并且它已经打开,你给他们一个错误消息像 'Another instance of the app is running. Bye' 那么他们就不会是一个非常快乐的用户。 你只需对该应用程序执行( 在一个GUI应用程序中) switch 并传入提供的参数- 或者如果 命令行 参数没有意义,那么你就必须弹出可能已经被最小化的应用程序。

框架已经支持这个- 它只是一个白痴命名为 DLL Microsoft.VisualBasic 并且它没有被放入 Microsoft.ApplicationUtils 或者类似的。 越过它- 或者打开反射器。

在这个太,提示:如果你用这种方法严格按照与参考资料 等等 就会想是时,已经有一个 App.xaml 请看一看罢。

这里代码应转到main方法。 查看这里的以获取关于WPF中主要方法的更多信息。


[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
 Process currentProcess = Process.GetCurrentProcess();
 var runningProcess = (from process in Process.GetProcesses()
 where
 process.Id!= currentProcess.Id &&
 process.ProcessName.Equals(
 currentProcess.ProcessName,
 StringComparison.Ordinal)
 select process).FirstOrDefault();
 if (runningProcess!= null)
 {
 ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
 return; 
 }
}

方法 2


static void Main()
{
 string procName = Process.GetCurrentProcess().ProcessName;
//get the list of all processes by that name

 Process[] processes=Process.GetProcessesByName(procName);

 if (processes.Length> 1)
 {
 MessageBox.Show(procName +" already running"); 
 return;
 } 
 else
 {
//Application.Run(...);
 }
}

下面是使用互斥和IPC内容的新方法,并将任何 命令行 参数传递给正在运行的实例:

http://blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/wpf-single-instance-application. aspx

我有一个一次性的类,它可以很容易的用于大多数用例:

像这样使用:


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
{
 #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
}

你不应该使用一个已经命名的互斥来实现一个实例应用程序( 或者至少不是生产代码) 。 恶意代码可以轻易地 DOS(Denial of Service) 。。

只是一些想法:有些情况下,需要一个应用程序的实例不是"lame",因为有些人可能会认为。 于单个用户能够访问数据库相关应用,等等 数据库是一个数量级的困难如果有一个允许的多个实例 app 。 首先,对于"名称冲突,不要使用人类可读的名称- 使用 GUID,或者更好的GUID +,也可以是人类可读的名称。 名称冲突刚好从雷达中弹出,互斥体不关心。 就像有人指出的那样,DOS攻击会很麻烦,但是如果恶意人员遇到了问题,并将它的合并到应用程序中,那么你仍然需要使用更多的信息来保护自己,而不是仅仅玩弄一个互斥的名字。 另外,如果使用以下变量的变体: 新的Mutex(true,"some GUID plus Name", out AIsFirstInstance), 你已经有了你的指示器,它是否是第一个实例。

这么多看似简单的问题的答案。 在这里稍微晃动一下,这就是我对此问题的解决方案。

创建一个互斥可能很麻烦,因为JIT-er只看到你在代码的一小部分中使用它,并希望将它标记为垃圾收集。 它非常想out-smart你认为你不会在那个长的时间里使用那个互斥。 事实上,只要你的应用程序运行,你就想挂上这个互斥。 最好的方式来告诉垃圾回收器把你单独互斥体,用来告诉它,它继续存在下去虽然是出来的不同代的车库集合中。 例如:


var m = new Mutex(...);
...
GC.KeepAlive(m);

我从这个页面提升了主意: http://www.ai.uga.edu/~mc/SingleInstance.html

...