在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
在。net下使用c#和WPF(而不是Windows窗体或控制台),创建一个只能作为单个实例运行的应用程序的正确方法是什么?
我知道它与某种叫做互斥的神秘事物有关,我很少能找到有人费心停下来解释其中一个是什么。
代码还需要通知已经运行的实例,用户试图启动第二个实例,如果存在命令行参数,还可能传递任何命令行参数。
当前回答
基于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
// ...
}
其他回答
这是我的两分钱
static class Program
{
[STAThread]
static void Main()
{
bool createdNew;
using (new Mutex(true, "MyApp", out createdNew))
{
if (createdNew) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainClass = new SynGesturesLogic();
Application.ApplicationExit += mainClass.tray_exit;
Application.Run();
}
else
{
var current = Process.GetCurrentProcess();
foreach (var process in Process.GetProcessesByName(current.ProcessName).Where(process => process.Id != current.Id))
{
NativeMethods.SetForegroundWindow(process.MainWindowHandle);
break;
}
}
}
}
}
这里有一个解决方案:
Protected Overrides Sub OnStartup(e As StartupEventArgs)
Const appName As String = "TestApp"
Dim createdNew As Boolean
_mutex = New Mutex(True, appName, createdNew)
If Not createdNew Then
'app is already running! Exiting the application
MessageBox.Show("Application is already running.")
Application.Current.Shutdown()
End If
MyBase.OnStartup(e)
End Sub
这是一篇关于互斥锁解决方案的很好的文章。本文描述的方法有两个优点。
首先,它不需要依赖于Microsoft。VisualBasic组装。如果我的项目已经依赖于该程序集,我可能会建议使用另一个答案中显示的方法。但事实上,我不使用微软。VisualBasic程序集,我宁愿不向项目添加不必要的依赖项。
其次,本文将展示当用户试图启动另一个实例时,如何将应用程序的现有实例显示到前台。这是这里描述的其他互斥锁解决方案没有解决的问题。
更新
截至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(正面部分)
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;
}
}
这就是我最终处理这个问题的方式。注意,调试代码仍然在那里进行测试。这段代码在App.xaml.cs文件的OnStartup中。(WPF)
// Process already running ?
if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
{
// Show your error message
MessageBox.Show("xxx is already running. \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);
// This process
Process currentProcess = Process.GetCurrentProcess();
// Get all processes running on the local computer.
Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
// ID of this process...
int temp = currentProcess.Id;
MessageBox.Show("This Process ID: " + temp.ToString());
for (int i = 0; i < localAll.Length; i++)
{
// Find the other process
if (localAll[i].Id != currentProcess.Id)
{
MessageBox.Show("Original Process ID (Switching to): " + localAll[i].Id.ToString());
// Switch to it...
SetForegroundWindow(localAll[i].MainWindowHandle);
}
}
Application.Current.Shutdown();
}
这可能有我还没有发现的问题。如果我遇到了,我会更新我的答案。
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,你也会想要看看这个。