在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
当前回答
我找到了一个更简单的解决方案,与戴尔·拉根的方法类似,但稍加修改。它基于标准的Microsoft WindowsFormsApplicationBase类,几乎可以做你需要的所有事情。
首先,你创建SingleInstanceController类,你可以在所有其他使用Windows窗体的单实例应用程序中使用它:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;
namespace SingleInstanceController_NET
{
public class SingleInstanceController
: WindowsFormsApplicationBase
{
public delegate Form CreateMainForm();
public delegate void StartNextInstanceDelegate(Form mainWindow);
CreateMainForm formCreation;
StartNextInstanceDelegate onStartNextInstance;
public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
{
// Set whether the application is single instance
this.formCreation = formCreation;
this.onStartNextInstance = onStartNextInstance;
this.IsSingleInstance = true;
this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);
}
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
if (onStartNextInstance != null)
{
onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
// for example, by clicking on the exe file.
} // This code can determine how to re-activate the existing main window of the running application.
}
protected override void OnCreateMainForm()
{
// Instantiate your main application form
this.MainForm = formCreation();
}
public void Run()
{
string[] commandLine = new string[0];
base.Run(commandLine);
}
}
}
然后你可以在你的程序中使用它,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;
namespace SingleInstance
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static Form CreateForm()
{
return new Form1(); // Form1 is used for the main window.
}
static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
// the main window is activated again.
{
mainWindow.WindowState = FormWindowState.Maximized;
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
controller.Run();
}
}
}
程序和SingleInstanceController_NET解决方案都应该引用Microsoft。VisualBasic。如果您只是想在用户试图重新启动正在运行的程序时重新激活正在运行的应用程序作为一个正常窗口,那么SingleInstanceController中的第二个参数可以为空。在给定的示例中,窗口是最大化的。
其他回答
从这里。
跨进程互斥锁的一个常见用途是确保一个程序一次只能运行一个实例。以下是如何做到的:
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();
}
}
}
互斥锁的一个很好的特性是,如果应用程序在没有首先调用ReleaseMutex的情况下终止,CLR将自动释放互斥锁。
您还可以使用CodeFluent Runtime,这是一组免费的工具。它提供了一个SingleInstance类来实现单个实例应用程序。
基于Matt Davis的答案,为方便起见包装成一个类。
public static class SingleAppInstanceChecker
{
/// <summary>
/// Arbitrary unique string
/// </summary>
private static Mutex _mutex = new Mutex(true, "0d12ad74-026f-40c3-bdae-e178ddee8602");
public static bool IsNotRunning()
{
return _mutex.WaitOne(TimeSpan.Zero, true);
}
}
使用示例:
private void Application_Startup(object sender, StartupEventArgs e)
{
if (!SingleAppInstanceChecker.IsNotRunning())
{
MessageBox.Show("Application is already running.");
// Exit application using:
// Environment.Exit(1);
// Application.Current.Shutdown();
// Etc...
return;
}
// Allow startup and continue with normal processing
// ...
}
你可以使用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();
}
}
Second, we will need to create a class that can manage our instances. Before we go through that, we are actually going to reuse some code that is in the Microsoft.VisualBasic assembly. Since, I am using C# in this example, I had to make a reference to the assembly. If you are using VB.NET, you don't have to do anything. The class we are going to use is WindowsFormsApplicationBase and inherit our instance manager off of it and then leverage properties and events to handle the single instancing.
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);
}
}
好吧,我希望您能够理解所有内容,能够使用这个实现并使其成为您自己的实现。
下面的代码是我的WCF命名管道解决方案,用于注册单实例应用程序。这很好,因为当另一个实例试图启动时,它也会引发一个事件,并接收另一个实例的命令行。
它是面向WPF的,因为它使用System.Windows.StartupEventHandler类,但这很容易修改。
这段代码需要引用PresentationFramework和System.ServiceModel。
用法:
class Program
{
static void Main()
{
var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");
if (SingleInstanceManager.VerifySingleInstance(applicationId))
{
SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;
// Start the application
}
}
static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
{
// Do something in response to another instance starting up.
}
}
源代码:
/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
/// <summary>
/// Raised when another instance attempts to start up.
/// </summary>
public static event StartupEventHandler OtherInstanceStarted;
/// <summary>
/// Checks to see if this instance is the first instance running on this machine. If it is not, this method will
/// send the main instance this instance's startup information.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if this instance is the main instance.</returns>
public static bool VerifySingleInstace(Guid guid)
{
if (!AttemptPublishService(guid))
{
NotifyMainInstance(guid);
return false;
}
return true;
}
/// <summary>
/// Attempts to publish the service.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
/// <returns>True if the service was published successfully.</returns>
private static bool AttemptPublishService(Guid guid)
{
try
{
ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
serviceHost.Open();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Notifies the main instance that this instance is attempting to start up.
/// </summary>
/// <param name="guid">The application's unique identifier.</param>
private static void NotifyMainInstance(Guid guid)
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
{
ISingleInstance singleInstance = factory.CreateChannel();
singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
}
}
/// <summary>
/// Creates an address to publish/contact the service at based on a globally unique identifier.
/// </summary>
/// <param name="guid">The identifier for the application.</param>
/// <returns>The address to publish/contact the service.</returns>
private static string CreateAddress(Guid guid)
{
return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
}
/// <summary>
/// The interface that describes the single instance service.
/// </summary>
[ServiceContract]
private interface ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
[OperationContract]
void NotifyMainInstance(string[] args);
}
/// <summary>
/// The implementation of the single instance service interface.
/// </summary>
private class SingleInstance : ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
public void NotifyMainInstance(string[] args)
{
if (OtherInstanceStarted != null)
{
Type type = typeof(StartupEventArgs);
ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(argsField != null);
argsField.SetValue(e, args);
OtherInstanceStarted(null, e);
}
}
}
}