创建单实例应用程序的正确方法是什么?
在.NET(而不是Windows Forms或者控制台)下使用Cand WPF,创建只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种称为互斥体的神话事物有关,我很少能找到一个烦人的人来阻止并解释其中的一个。
该代码还需要告知已经运行的实例用户试图启动第二个实例,并且还可能传递任何命令行参数(如果存在)。
解决方案
回答
从这里。
跨进程Mutex的常见用法是确保一次只能运行程序实例。这是完成的过程:
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。
回答
我们可以使用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程序集中的某些代码。由于在此示例中使用的是Cin,因此必须引用该程序集。如果我们使用的是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方法的调用将其传递给应用程序。
第三,是时候创建我们的EntryPoint了。与其像通常那样更新应用程序,不如利用我们的SingleInstanceManager。
public class EntryPoint { [STAThread] public static void Main(string[] args) { SingleInstanceManager manager = new SingleInstanceManager(); manager.Run(args); } }
好吧,我希望我们能够了解所有内容并能够使用此实现并将其实现为自己的实现。
回答
永远不要使用命名的互斥体来实现单实例应用程序(或者至少不用于生产代码)。恶意代码可以轻易地对资产进行DoS(拒绝服务)...
回答
这是一篇有关Mutex解决方案的非常好的文章。该文章描述的方法有两个方面的优势。
首先,它不需要依赖于Microsoft.VisualBasic程序集。如果我的项目已经依赖于该程序集,那么我可能会主张使用公认的答案中所示的方法。但实际上,我不使用Microsoft.VisualBasic程序集,而我不想在项目中添加不必要的依赖项。
其次,本文介绍了当用户尝试启动另一个实例时如何将应用程序的现有实例置于前台。这是一个非常不错的效果,此处所述的其他Mutex解决方案均未解决。
更新
截至2014年8月1日,我上面链接的文章仍处于活动状态,但该博客已有一段时间没有更新。这使我担心,最终它可能会消失,并且随之而来的是所倡导的解决方案。我在此转载本文的内容,以供后代参考。这些单词仅属于Sanity Free Coding的博客所有者。
Today I wanted to refactor some code that prohibited my application from running multiple instances of itself. Previously I had use System.Diagnostics.Process to search for an instance of my myapp.exe in the process list. While this works, it brings on a lot of overhead, and I wanted something cleaner. Knowing that I could use a mutex for this (but never having done it before) I set out to cut down my code and simplify my life. In the class of my application main I created a static named Mutex:
static class Program { static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}"); [STAThread] ... }
Having a named mutex allows us to stack synchronization across multiple threads and processes which is just the magic I'm looking for. Mutex.WaitOne has an overload that specifies an amount of time for us to wait. Since we're not actually wanting to synchronizing our code (more just check if it is currently in use) we use the overload with two parameters: Mutex.WaitOne(Timespan timeout, bool exitContext). Wait one returns true if it is able to enter, and false if it wasn't. In this case, we don't want to wait at all; If our mutex is being used, skip it, and move on, so we pass in TimeSpan.Zero (wait 0 milliseconds), and set the exitContext to true so we can exit the synchronization context before we try to aquire a lock on it. Using this, we wrap our Application.Run code inside something like this:
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"); } } }
So, if our app is running, WaitOne will return false, and we'll get a message box. Instead of showing a message box, I opted to utilize a little Win32 to notify my running instance that someone forgot that it was already running (by bringing itself to the top of all the other windows). To achieve this I used PostMessage to broadcast a custom message to every window (the custom message was registered with RegisterWindowMessage by my running application, which means only my application knows what it is) then my second instance exits. The running application instance would receive that notification and process it. In order to do that, I overrode WndProc in my main form and listened for my custom notification. When I received that notification I set the form's TopMost property to true to bring it up on top. Here is what I ended up with: 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 (front side partial)
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; } }
回答
只是一些想法:
在某些情况下,仅要求应用程序的一个实例不像某些人所认为的那样是" lam毛"。如果一个数据库允许单个用户访问该应用程序的多个实例,那么数据库应用程序等的难度将增加一个数量级(我们知道,所有这些更新都会更新用户在该应用程序的多个实例中打开的所有记录。机器等)。
回答
首先,对于"名称冲突",不要使用人类可读的名称,而应使用GUID代替,甚至最好使用GUID +人类可读的名称。名称冲突的可能性刚刚消失,而Mutex不在乎。正如某人指出的那样,DOS攻击会很糟糕,但是如果恶意程序遇到了获取互斥量名称并将其合并到他们的应用程序中的麻烦,那么无论如何我们都是目标,并且必须做更多的事情来保护自己而不只是摆弄一个互斥量名称。
另外,如果使用以下变量:
新的Mutex(true,"某些GUID加名称",出自AIsFirstInstance),我们已经有了关于Mutex是否是第一个实例的指示器。
好吧,我为此有一个可弃用的类,在大多数情况下都可以轻松使用:
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 }
回答
这里是:
这个看似简单的问题有这么多答案。我只是在这里稍作改动,这就是我对这个问题的解决方案。
var m = new Mutex(...); ... GC.KeepAlive(m);
创建Mutex可能会很麻烦,因为JIT-er只看到我们将其用于代码的一小部分,并希望将其标记为可进行垃圾回收。它非常想超越想法,以为我们将不再使用该Mutex。实际上,只要应用程序正在运行,我们就希望挂在此Mutex上。告诉垃圾收集器让我们自己离开Mutex的最好方法是告诉它,使其在不同年代的车库收集中保持活力。例子:
回答
我从以下页面提出了这个想法:http://www.ai.uga.edu/~mc/SingleInstance.html
回答
WPF单实例应用程序是一种使用Mutex和IPC东西,并且还将任何命令行参数传递给正在运行的实例的新方法。
public partial class App { [DllImport("user32")] private static extern int OpenIcon(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var p = Process .GetProcessesByName(Process.GetCurrentProcess().ProcessName); foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero)) { OpenIcon(t.MainWindowHandle); SetForegroundWindow(t.MainWindowHandle); Current.Shutdown(); return; } // there is a chance the user tries to click on the icon repeatedly // and the process cannot be discovered yet bool createdNew; var mutex = new Mutex(true, "MyAwesomeApp", out createdNew); // must be a variable, though it is unused - // we just need a bit of time until the process shows up if (!createdNew) { Current.Shutdown(); return; } new Bootstrapper().Run(); } }
回答
这是我用的。它结合了过程枚举来执行切换和互斥,以防止"活动的点击器":
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.
- 对于C#,人们深吸一口气,然后忽略整个"我不想包含VisualBasic DLL"。因此,正如Scott Hanselman所说的那样,事实上,这几乎是解决问题的最干净的方法,并且它是由对框架了解得比我们多的人设计的。
- 从可用性的角度来看,事实是用户是否正在加载应用程序并且该应用程序已经打开,并且我们正在向他们显示错误消息,例如"'该应用程序的另一个实例正在运行"。再见,那么他们将不会是一个非常快乐的用户。我们只需(在GUI应用程序中)切换到该应用程序并传递提供的参数-否则,如果命令行参数没有意义,则我们必须弹出可能已最小化的应用程序。
MSDN实际上为两个Cand VB都提供了一个示例应用程序来完全做到这一点:http://msdn.microsoft.com/zh-cn/library/ms771662(v=VS.90).aspx
该框架已经对此提供了支持,只是有些白痴将其命名为DLL" Microsoft.VisualBasic",却没有放入" Microsoft.ApplicationUtils"之类。克服它或者打开Reflector。
段落数量不匹配