互斥锁实现阻止应用多开。
以下是WPF程序
csharp
using System.Configuration;
using System.Data;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private static Mutex? _appMutex;
protected override void OnStartup(StartupEventArgs e)
{
const string mutexName = "Local\\{YOUR-UNIQUE-GUID}_WpfApp1_Singleton";
bool createdNew = false;
try
{
_appMutex = new Mutex(true, mutexName, out createdNew);
}
catch (UnauthorizedAccessException)
{
Shutdown();
return;
}
if (!createdNew)
{
Shutdown();
return;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
_appMutex?.Dispose();
base.OnExit(e);
}
}
}
启动第一个实例之后,第二个就不能再次启动。
细节
代码很简单,就是一个锁而已。但这里面有很多的细节。
启动两次应用程序是两个进程,而代码中除了初始化锁之外,没有加锁的操作 以及释放锁的操作?这是如何保证只能启动一个实例的哪?
为什么放在App类里面 而不是放在其他的里面
private static Mutex? _appMutex; 为什么要加static
除了使用锁,信号量 事件可以实现同样的功能吗
Local\{YOUR-UNIQUE-GUID}_WpfApp1_Singleton Local有什么用?
锁使用了内核,不通过内核,能实现吗? 锁是内核层的实现,需要从.net托管到native,再到内核层 然后再原路返回 性能相对较差
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
namespace WpfApp1
{
public partial class App : Application
{
// 1. 定义全局唯一标识(替换为自己的GUID)
private const string UniqueAppGuid = "6B29FC40-CA47-1067-B31D-00DD010662DA";
private const string MmfName = $"Local\\CAS_Singleton_{UniqueAppGuid}";
private MemoryMappedFile? _mmf;
private MemoryMappedViewAccessor? _viewAccessor;
// 2. 原子标记的偏移量(共享内存中存储一个int值:0=空闲,1=占用)
private const int FlagOffset = 0;
protected override unsafe void OnStartup(StartupEventArgs e)
{
try
{
// 3. 创建/打开共享内存(4字节足够存储int标记)
_mmf = MemoryMappedFile.CreateOrOpen(MmfName, 4, MemoryMappedFileAccess.ReadWrite);
_viewAccessor = _mmf.CreateViewAccessor(0, 4, MemoryMappedFileAccess.ReadWrite);
// 4. CAS原子操作:尝试将标记从0改为1
int expected = 0;
int desired = 1;
byte* pointer = null;
_viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
try
{
// pointer 是视图起始处的字节指针,FlagOffset 为字节偏移
int* intPtr = (int*)(pointer + FlagOffset);
// 使用 Interlocked 在内存映射区域上进行 CAS
int original = Interlocked.CompareExchange(ref *intPtr, desired, expected);
if (original != 0)
{
// CAS失败:已有实例运行,激活已有实例并退出
ActivateExistingInstance();
CleanupResources();
Shutdown();
return;
}
}
finally
{
_viewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
// CAS成功:当前是第一个实例,正常启动
base.OnStartup(e);
}
catch (UnauthorizedAccessException)
{
// 无权限访问共享内存(已有实例占用)
ActivateExistingInstance();
Shutdown();
}
catch (Exception ex)
{
MessageBox.Show($"启动失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
CleanupResources();
Shutdown();
}
}
/// <summary>
/// 激活已有实例窗口(可选,提升用户体验)
/// </summary>
private void ActivateExistingInstance()
{
// 结合窗口API查找并激活已有实例(复用之前的窗口激活逻辑)
const int SW_RESTORE = 9;
IntPtr hwnd = FindWindowByClassNameOrTitle(UniqueAppGuid);
if (hwnd != IntPtr.Zero)
{
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
}
}
protected override unsafe void OnExit(ExitEventArgs e)
{
// 5. 程序退出时:CAS还原标记为0,释放共享内存
if (_viewAccessor != null)
{
byte* pointer = null;
_viewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
try
{
int* intPtr = (int*)(pointer + FlagOffset);
Interlocked.Exchange(ref *intPtr, 0);
}
finally
{
_viewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
CleanupResources();
base.OnExit(e);
}
/// <summary>
/// 释放共享内存资源
/// </summary>
private void CleanupResources()
{
_viewAccessor?.Dispose();
_mmf?.Dispose();
}
// ========== 辅助API(窗口操作) ==========
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern IntPtr FindWindow(string? lpClassName, string lpWindowName);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
/// <summary>
/// 根据唯一标识查找窗口
/// </summary>
private IntPtr FindWindowByClassNameOrTitle(string uniqueId)
{
// 给MainWindow设置标题包含uniqueId,便于查找
return FindWindow(null, $"WpfApp1_MainWindow_{uniqueId}");
}
}
}
这段代可修改性如何 如果换方案哪? 不止有这一种方案。