这是我到目前为止实现的用于创建单个实例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}}}{IDisposable
}", 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
接口,因为我使用的是Mutex
成员(Dispose()
)。我的m_Mutex = new Mutex(true, mutexName, out mutexCreated);
实施是否足够好?我应该避免它,因为它永远不会被调用吗?
2)最好使用m_Mutex = new Mutex(false, mutexName);
并检查结果或使用m_Mutex.WaitOne(TimeSpan.Zero, false);
,然后检查RegisterWindowMessage
?在多线程的情况下,我的意思是......
3)UInt32
API调用应返回HwndSourceHook
...但Int32
仅接受Int32.MaxValue
作为消息值...我是否应该担心意外行为(比如结果大于OnStartup
)?
4)在base.OnStartup(e);
覆盖中......即使另一个实例已经在运行并且我要关闭应用程序,我应该执行Topmost
吗?
5)是否有更好的方法将现有实例置于不需要设置Activate()
值的顶部?也许OnStartup
?
6)你能看到我的方法有任何缺陷吗?关于多线程,坏的异常处理和类似的东西?例如......如果我的应用程序在OnExit
和{{1}}之间崩溃会发生什么?
答案 0 :(得分:40)
有几种选择,
<强>互斥强>
Mutex myMutex ;
private void Application_Startup(object sender, StartupEventArgs e)
{
bool aIsNewInstance = false;
myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);
if (!aIsNewInstance)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
流程经理
private void Application_Startup(object sender, StartupEventArgs e)
{
Process proc = Process.GetCurrentProcess();
int count = Process.GetProcesses().Where(p=>
p.ProcessName == proc.ProcessName).Count();
if (count > 1)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
使用侦听器套接字
向另一个应用程序发出信号的一种方法是打开一个Tcp连接。创建套接字,绑定到端口,然后在后台线程上侦听连接。如果成功,请正常运行。如果没有,则建立与该端口的连接,该端口向另一个实例发出第二次应用程序启动尝试的信号。如果合适,原始实例可以将其主窗口置于前面。
“安全”软件/防火墙可能是个问题。
答案 1 :(得分:28)
我希望获得更好的用户体验 - 如果另一个实例已在运行,请让它激活,而不是显示有关第二个实例的错误。这是我的实施。
我使用命名的Mutex来确保只有一个实例正在运行并命名为EventWaitHandle以将通知从一个实例传递到另一个实例。
App.xaml.cs:
/// <summary>Interaction logic for App.xaml</summary>
public partial class App
{
#region Constants and Fields
/// <summary>The event mutex name.</summary>
private const string UniqueEventName = "{GUID}";
/// <summary>The unique mutex name.</summary>
private const string UniqueMutexName = "{GUID}";
/// <summary>The event wait handle.</summary>
private EventWaitHandle eventWaitHandle;
/// <summary>The mutex.</summary>
private Mutex mutex;
#endregion
#region Methods
/// <summary>The app on startup.</summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void AppOnStartup(object sender, StartupEventArgs e)
{
bool isOwned;
this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
// So, R# would not give a warning that this variable is not used.
GC.KeepAlive(this.mutex);
if (isOwned)
{
// Spawn a thread which will be waiting for our event
var thread = new Thread(
() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke(
(Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
}
});
// It is important mark it as background otherwise it will prevent app from exiting.
thread.IsBackground = true;
thread.Start();
return;
}
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
#endregion
}
MainWindow.cs中的BringToForeground:
/// <summary>Brings main window to foreground.</summary>
public void BringToForeground()
{
if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
{
this.Show();
this.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
this.Activate();
this.Topmost = true;
this.Topmost = false;
this.Focus();
}
并添加Startup =&#34; AppOnStartup&#34; (谢谢vhanla!):
<Application x:Class="MyClass.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="AppOnStartup">
<Application.Resources>
</Application.Resources>
</Application>
适合我:)
答案 2 :(得分:28)
对于WPF,只需使用:
public partial class App : Application
{
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "MyAppName";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
}
答案 3 :(得分:7)
1)它看起来像是一个标准的Dispose实现。这不是必要的(见第6点),但它没有任何伤害。 (清理关闭它有点像清理房子然后烧掉它,恕我直言,但对此事的意见不同......)
无论如何,为什么不使用“Dispose”作为清理方法的名称,即使它没有被直接调用?您可以将其称为“清理”,但请记住您也为人类编写代码,Dispose看起来很熟悉,.NET上的任何人都了解它的用途。所以,去“Dispose”。
2)我一直看到m_Mutex = new Mutex(false, mutexName);
我认为这更像是一种技术优势的惯例。
3)来自MSDN:
如果消息成功注册,则返回值是0xC000到0xFFFF范围内的消息标识符。
所以我不担心。通常,对于这类函数,UInt不用于“它不适合Int,让我们使用UInt,所以我们有更多东西”但澄清合同“函数永远不会返回负值”。
4)如果你关机,我会避免打电话,原因与#1
相同5)有几种方法可以做到这一点。 Win32中最简单的方法就是让第二个实例调用SetForegroundWindow(看这里:http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx);但是,我不知道是否有等效的WPF功能,或者你是否需要PInvoke它。
6)
例如......如果我的应用程序在OnStartup和OnExit之间崩溃会发生什么?
没关系:当进程终止时,进程拥有的所有句柄都被释放;互斥体也被释放了。
简而言之,我的建议:
例如,您可以使用您的技术(尝试向窗口发送/发送消息 - 如果没有回复它被卡住),加上MSK技术,以查找和终止旧进程。然后正常开始。
答案 4 :(得分:5)
最直接的处理方式是使用命名信号量。 尝试这样的事情......
public partial class App : Application
{
Semaphore sema;
bool shouldRelease = false;
protected override void OnStartup(StartupEventArgs e)
{
bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);
if (result) // we have another instance running
{
App.Current.Shutdown();
}
else
{
try
{
sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
}
catch
{
App.Current.Shutdown(); //
}
}
if (!sema.WaitOne(0))
{
App.Current.Shutdown();
}
else
{
shouldRelease = true;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
if (sema != null && shouldRelease)
{
sema.Release();
}
}
}
答案 5 :(得分:4)
我已经使用了一个简单的TCP套接字(在Java中,10年前)。
答案 6 :(得分:4)
为防止再次发生
可以这样做(对于WPF应用程序,请参见参考App(),但也可以在WinForms上使用):
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
preventSecond();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private void preventSecond()
{
try
{
EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
}
}
}
第二个版本:在上面加上信号通知另一个实例以显示窗口(更改WinForms的MainWindow部分):
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
//preventSecond();
SingleInstanceWatcher();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private EventWaitHandle eventWaitHandle;
/// <summary>prevent a second instance and signal it to bring its mainwindow to foregorund</summary>
/// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
private void SingleInstanceWatcher()
{
// check if it is allready open.
try
{
// try to open it - if another instance is running, it will exist
this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
// listen to a new event
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
}
// if this instance gets the signal to show the main window
new Task(() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke((Action)(() =>
{
// could be set or removed anytime
if (!Current.MainWindow.Equals(null))
{
var mw = Current.MainWindow;
if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
{
mw.Show();
mw.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
mw.Activate();
mw.Topmost = true;
mw.Topmost = false;
mw.Focus();
}
}));
}
})
.Start();
}
}
此代码作为课程的一部分,将是@ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.cs
答案 7 :(得分:2)
这是一个简单的解决方案, 打开您的启动文件(从应用程序启动的位置查看),在本例中为MainWindow.xaml。 打开MainWindow.xaml.cs文件。转到构造函数,并在intializecomponent()之后添加以下代码:
Process Currentproc = Process.GetCurrentProcess();
Process[] procByName=Process.GetProcessesByName("notepad"); //Write the name of your exe file in inverted commas
if(procByName.Length>1)
{
MessageBox.Show("Application is already running");
App.Current.Shutdown();
}
不要忘记添加System.Diagnostics
答案 8 :(得分:1)
我的.Net Core 3 Wpf单实例应用程序解决方案:
[STAThread]
public static void Main()
{
StartSingleInstanceApplication<CntApplication>();
}
public static void StartSingleInstanceApplication<T>()
where T : RichApplication
{
DebuggerOutput.GetInstance();
Assembly assembly = typeof(T).Assembly;
string mutexName = $"SingleInstanceApplication/{assembly.GetName().Name}/{assembly.GetType().GUID}";
Mutex mutex = new Mutex(true, mutexName, out bool mutexCreated);
if (!mutexCreated)
{
mutex = null;
var client = new NamedPipeClientStream(mutexName);
client.Connect();
using (StreamWriter writer = new StreamWriter(client))
writer.Write(string.Join("\t", Environment.GetCommandLineArgs()));
return;
}
else
{
T application = Activator.CreateInstance<T>();
application.Exit += (object sender, ExitEventArgs e) =>
{
mutex.ReleaseMutex();
mutex.Close();
mutex = null;
};
Task.Factory.StartNew(() =>
{
while (mutex != null)
{
using (var server = new NamedPipeServerStream(mutexName))
{
server.WaitForConnection();
using (StreamReader reader = new StreamReader(server))
{
string[] args = reader.ReadToEnd().Split("\t", StringSplitOptions.RemoveEmptyEntries).ToArray();
UIDispatcher.GetInstance().Invoke(() => application.ExecuteCommandLineArgs(args));
}
}
}
}, TaskCreationOptions.LongRunning);
typeof(T).GetMethod("InitializeComponent").Invoke(application, new object[] { });
application.Run();
}
}
答案 9 :(得分:0)
这是将旧实例带到前景的示例:
public partial class App : Application
{
[DllImport("user32", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string cls, string win);
[DllImport("user32")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32")]
static extern bool OpenIcon(IntPtr hWnd);
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
private static void ActivateOtherWindow()
{
var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
if (other != IntPtr.Zero)
{
SetForegroundWindow(other);
if (IsIconic(other))
OpenIcon(other);
}
}
}
但只有当您的主窗口标题不会改变durig运行时才会起作用。
修改强>
您还可以在Startup
中使用App.xaml
事件,而不是覆盖OnStartup
。
// App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
}
// App.xaml
<Application x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event
请记住在这种情况下不要致电base.OnStartup(e)
!
答案 10 :(得分:0)
把我的帽子扔进这里。我要做的是创建常规ApplicationBase
类的Application
子类,并将其保存在所有WPF应用程序中使用的公共库中。然后,我更改基类(从XAML及其背后的代码中)以使用我的基类。最后,我将EntryPoint.Main
用作应用程序的启动对象,然后检查单个实例的状态,如果不是第一个实例,则简单地返回。
注意:我还将展示如何支持一个标志,如果您要启动另一个实例,该标志使您可以覆盖该标志。但是,请谨慎使用此选项。仅在实际可行的地方使用它。
这是代码:
public abstract class ApplicationBase : Application {
public static string? SingleInstanceId { get; private set; }
public static bool InitializeAsFirstInstance(string singleInstanceId){
if(SingleInstanceId != null)
throw new AlreadyInitializedException(singleInstanceId);
SingleInstanceId = singleInstanceId;
var waitHandleName = $"SingleInstanceWaitHandle:{singleInstanceId}";
if(EventWaitHandle.TryOpenExisting(waitHandleName, out var waitHandle)){
// An existing WaitHandle was successfuly opened which means we aren't the first so signal the other
waitHandle.Set();
// Then indicate we aren't the first instance by returning false
return false;
}
// Welp, there was no existing WaitHandle with this name, so we're the first!
// Now we have to set up the EventWaitHandle in a task to listen for other attempts to launch
void taskBody(){
var singleInstanceWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, waitHandleName);
while (singleInstanceWaitHandle.WaitOne()) {
if(Current is ApplicationBase applicationBase)
Current.Dispatcher.BeginInvoke(applicationBase.OtherInstanceLaunched);
}
}
new Task(taskBody, TaskCreationOptions.LongRunning).Start();
return true;
}
public static bool IsSingleInstance
=> SingleInstanceId != null;
protected virtual void OtherInstanceLaunched()
=> Current.MainWindow?.BringToFront();
}
通过将OtherInstanceLaunched
标记为虚拟,我可以基于每个应用程序对其进行自定义,方法是简单地对其进行覆盖,或者让默认实现来完成它,这是{{1}上的扩展方法},我添加了。 (从本质上讲,它确保它是可见的,还原的,然后对其进行聚焦。)
Window
此方法的优点是,即使在实例化应用程序本身之前以及在显示初始屏幕之前,它也可以移交执行。换句话说,它可以尽早解决。
注意:如果您甚至不需要多重支持,则可以删除该参数检查和测试。只是出于说明目的添加了