什么是创建单一的正确方法
在.NET(而不是Windows窗体或控制台)下使用C#和WPF,创建只能作为单个实例运行的应用程序的正确方法是什么?
我知道这与一些称为互斥体的神秘事物有关,我很少能找到一个能够阻止并解释其中之一的人。
代码还需要通知已经运行的实例,用户试图启动第二个实例,并且可能还会传递任何命令行参数(如果存在的话)。
这是一篇关于互斥解决方案的非常好的文章。 由于两个原因,文章描述的方法是有利的。
首先,它不需要依赖于Microsoft.VisualBasic程序集。 如果我的项目已经依赖于该程序集,我可能会主张使用接受答案中显示的方法。 但事实上,我没有使用Microsoft.VisualBasic程序集,我宁愿不添加不必要的依赖项到我的项目中。
其次,文章展示了当用户尝试启动另一个实例时,如何将应用程序的现有实例置于前台。 这是一个非常好的触摸,这里描述的其他Mutex解决方案没有解决。
UPDATE
截至2014年8月1日,我上面链接的文章仍然有效,但博客一段时间未更新。 这让我担心,最终它可能会消失,并伴随它而提出主张的解决方案。 我在这里为后人重现了这篇文章的内容。 这些词完全属于Sanity Free Coding的博客所有者。
今天,我想重构一些禁止我的应用程序运行自己的多个实例的代码。
以前,我使用System.Diagnostics.Process在进程列表中搜索myapp.exe的实例。 虽然这有效,但它带来了很多开销,我想要更清洁的东西。
知道我可以为此使用一个互斥锁(但之前从未做过),我开始削减我的代码并简化了我的生活。
在我的应用程序主类中,我创建了一个名为Mutex的静态:
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
...
}
拥有一个命名的互斥锁使我们能够跨多个线程和进程堆栈同步,这正是我所需要的魔法。
Mutex.WaitOne有一个超载,指定我们等待的时间。 由于我们实际上并不想同步我们的代码(更多的是检查它是否正在使用),我们使用带有两个参数的重载:Mutex.WaitOne(Timespan timeout,bool exitContext)。 如果能够输入,等待一个返回true;如果不是,则返回false。 在这种情况下,我们不想等待; 如果正在使用我们的互斥锁,请跳过它,然后继续,因此我们传入TimeSpan.Zero(等待0毫秒),并将exitContext设置为true,以便在尝试锁定锁定之前退出同步上下文。 使用这个,我们将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将返回false,并且我们会得到一个消息框。
我没有显示消息框,而是选择使用一个小的Win32来通知我的正在运行的实例,有人忘记了它已经在运行(通过将自己带到所有其他窗口的顶部)。 为了实现这一点,我使用PostMessage向每个窗口广播自定义消息(自定义消息由运行的应用程序通过RegisterWindowMessage注册,这意味着只有我的应用程序知道它是什么),然后我的第二个实例退出。 正在运行的应用程序实例将收到该通知并进行处理。 为了做到这一点,我以我的主要形式覆盖了WndProc,并听取了我的自定义通知。 当我收到该通知时,我将表单的TopMost属性设置为true,将其置于顶端。
这是我最终的结果:
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);
}
}
}
// 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);
}
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;
}
}
你可以使用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程序集中的一些代码。 因为我在这个例子中使用了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。 正如你所看到的,我可以通过事件参数来了解通过命令行传递的内容。 我将该值设置为实例字段。 您可以在这里解析命令行,或者您可以通过构造函数和对Activate方法的调用将它传递给应用程序。
第三,现在是创建我们的入口点的时候了。 我们将利用SingleInstanceManager,而不是像通常那样新增应用程序。
public class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
SingleInstanceManager manager = new SingleInstanceManager();
manager.Run(args);
}
}
那么,我希望你能够遵循一切,并能够使用这个实现并使其成为你自己的。
从这里。
跨进程互斥的常见用途是确保一次只能运行一个程序的实例。 这是如何完成的:
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。
链接地址: http://www.djcxy.com/p/1451.html