这是我到目前为止实现的代码,用于创建单个实例WPF应用程序:
#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion
namespace MyWPF
{
public partial class MainApplication : Application, IDisposable
{
#region Members
private Int32 m_Message;
private Mutex m_Mutex;
#endregion
#region Methods: Functions
private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
{
if (message == m_Message)
{
if (MainWindow.WindowState == WindowState.Minimized)
MainWindow.WindowState = WindowState.Normal;
Boolean topmost = MainWindow.Topmost;
MainWindow.Topmost = true;
MainWindow.Topmost = topmost;
}
return IntPtr.Zero;
}
private void Dispose(Boolean disposing)
{
if (disposing && (m_Mutex != null))
{
m_Mutex.ReleaseMutex();
m_Mutex.Close();
m_Mutex = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Methods: Overrides
protected override void OnStartup(StartupEventArgs e)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Boolean mutexCreated;
String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name);
m_Mutex = new Mutex(true, mutexName, out mutexCreated);
m_Message = NativeMethods.RegisterWindowMessage(mutexName);
if (!mutexCreated)
{
m_Mutex = null;
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);
Current.Shutdown();
return;
}
base.OnStartup(e);
MainWindow window = new MainWindow();
MainWindow = window;
window.Show();
HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
}
protected override void OnExit(ExitEventArgs e)
{
Dispose();
base.OnExit(e);
}
#endregion
}
}
一切都很完美......但我对它有一些疑问,我想收到你关于如何改进我的方法的建议 .
1)Code Analysis要求我实现 IDisposable
接口,因为我使用的是 IDisposable
成员( Mutex
) . 我的 Dispose()
实施是否足够好?我应该避免它,因为它永远不会被调用吗?
2)最好使用 m_Mutex = new Mutex(true, mutexName, out mutexCreated);
并检查结果或使用 m_Mutex = new Mutex(false, mutexName);
,然后检查 m_Mutex.WaitOne(TimeSpan.Zero, false);
?在多线程的情况下,我的意思是......
3) RegisterWindowMessage
API调用应返回 UInt32
...但 HwndSourceHook
仅接受 Int32
作为消息值...我是否应该担心意外行为(比如大于 Int32.MaxValue
的结果)?
4)在 OnStartup
覆盖中...我应该执行 base.OnStartup(e);
,即使另一个实例已在运行并且我要关闭应用程序?
5)是否有更好的方法将现有实例置于不需要设置 Topmost
值的顶部?也许 Activate()
?
6)你能看到我的方法有什么缺陷吗?关于多线程,坏的异常处理和类似的东西?例如......如果我的应用程序在 OnStartup
和 OnExit
之间崩溃会发生什么?
9 回答
1)它看起来像我的标准Dispose实现 . 这不是必要的(见第6点),但它没有任何伤害 . (清理关闭它有点像清理房子,然后烧掉它,恕我直言,但对此事的意见不同......)
无论如何,为什么不使用“Dispose”作为清理方法的名称,即使它没有被直接调用?您可以将其称为“清理”,但请记住您也为人类编写代码,Dispose看起来很熟悉,.NET上的任何人都了解它的用途 . 所以,去“Dispose” .
2)我一直看到
m_Mutex = new Mutex(false, mutexName);
我认为这更像是一种技术优势的惯例 .3)来自MSDN:
所以我不担心 . 通常,对于这类函数,UInt不用于“它不适合Int,让我们使用UInt所以我们有更多东西”但澄清 Contract “函数永远不会返回负值” .
4)如果你关机,我会避免打电话,原因与#1相同
5)有几种方法可以做到这一点 . Win32中最简单的方法就是让第二个实例调用SetForegroundWindow(看这里:http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx);但是,我不知道是否有等效的WPF功能,或者你是否需要PInvoke它 .
6)
没关系:当进程终止时,进程拥有的所有句柄都被释放;互斥体也被释放了 .
总之,我的建议:
我会使用一种基于命名同步对象的方法:它是在Windows平台上 Build 的 . (在考虑多用户系统时要小心,比如终端服务器!将同步对象命名为,可能是用户名/ SID和应用程序名称的组合)
使用Windows API引发上一个实例(请参阅我在第5点的链接)或WPF等效项 .
你可能不必担心崩溃(内核会为你减少内核对象的ref计数器;无论如何都做一点测试),但是如果我可以建议一个改进:如果你的第一个应用程序实例没有崩溃但是挂起怎么办? ? (发生在Firefox上......我确定它也发生在你身上!没有窗口,ff进程,你无法打开一个新的) . 在这种情况下,将另一种技术或两种技术结合起来可能是好的,a)测试应用程序/窗口是否响应; b)找到挂起的实例并终止它
例如,您可以使用您的技术(尝试向窗口发送/发送消息 - 如果没有回复它被卡住),加上MSK技术,以查找和终止旧进程 . 然后正常开始 .
有几种选择,
Mutex
流程经理
命名为信号量
使用侦听器套接字
Mutex
Process manager
Use a listener socket
向另一个应用程序发出信号的一种方法是打开一个Tcp连接 . 创建套接字,绑定到端口,然后在后台线程上侦听连接 . 如果成功,请正常运行 . 如果没有, Build 与该端口的连接,这标志着另一个实例已经进行了第二次应用程序启动尝试 . 如果合适,原始实例可以将其主窗口置于前面 .
“安全”软件/防火墙可能是一个问题 .
Single Instance Application C#.Net along with Win32
对于WPF,只需使用:
我希望有一个更好的用户体验 - 如果另一个实例已经在运行,让我们激活它,而不是显示有关第二个实例的错误 . 这是我的实施 .
我使用命名的Mutex来确保只有一个实例正在运行并命名为EventWaitHandle以将通知从一个实例传递到另一个实例 .
App.xaml.cs:
和MainWindow.cs中的BringToForeground:
并添加Startup =“AppOnStartup”(谢谢vhanla!):
适合我:)
处理它的最直接的方法是使用命名信号量 . 尝试这样的事......
我已经使用了一个简单的TCP套接字(在Java中,10年前) .
启动时连接到预定义端口,如果连接被接受,则另一个实例正在运行,如果没有,则启动TCP侦听器
有人连接到你后,弹出窗口并断开连接
这是一个简单的解决方案,打开您的启动文件(从应用程序启动的位置查看),在本例中为MainWindow.xaml . 打开MainWindow.xaml.cs文件 . 转到构造函数,并在intializecomponent()之后添加以下代码:
不要忘记添加System.Diagnostics
防止第二个实例,不需要Mutex代码,使用Task,没有TCP,没有Pinvokes,线程保存 - 它可以这样做(这对于WPF应用程序(请参阅参考App()),但也适用于WinForms ):
第二个版本:上面加上信号通知另一个实例来显示窗口(更改WinForms的MainWindow部分):
这段代码作为一个下降类,将是@ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.cs
这是将旧实例带到前台的示例:
但它只有在你的主窗口 Headers 不改变durig运行时才会起作用 .
Edit:
您也可以在
App.xaml
中使用Startup
事件,而不是覆盖OnStartup
.在这种情况下,请记住不要拨打
base.OnStartup(e)
!