直接用Mutex最可靠,但createdNew不能代替WaitOne判断锁状态,且默认命名不跨用户会话;必须用WaitOne()检测是否真正获取锁,跨会话需加Global\前缀并提权,推荐Local\+唯一名称。

直接用 Mutex 是最可靠的方式,但 createdNew 参数不能代替锁状态判断,且默认命名不跨用户会话——这是绝大多数人第一次写就翻车的地方。
为什么不能只看 createdNew?
createdNew 只表示“这个名字的 Mutex 是不是本次创建的”,它不反映当前是否能拿到锁。比如另一个进程已持有该 Mutex 但没释放,你新建时 createdNew 仍为 true(因为内核对象存在但无人持有),结果你的程序误以为可以启动。
正确做法是必须调用 WaitOne():
-
WaitOne(0):非阻塞尝试获取,立刻返回true(拿到)或false(被占) -
WaitOne(1000):最多等 1 秒,超时返回false - 永远不要依赖
createdNew做主逻辑判断
跨用户会话必须加 Global\ 前缀
Windows 默认把命名 Mutex 创建在当前会话(Session)下,不同用户登录、远程桌面、服务账户运行时互不可见。不加前缀,多用户环境下等于没防。
但直接写 "Global\MyApp" 会抛 UnauthorizedAccessException ——非管理员进程默认无权创建全局对象。
- 解决方案:用
"Local\MyApp"(推荐)——同一会话内有效,覆盖 95% 桌面场景 - 真要跨会话:改用
"Global\MyApp",并确保应用以管理员权限运行,或在 manifest 中声明requireAdministrator - 名称必须足够唯一,避免和其它软件冲突,比如用反向域名:
"Local\com.mycompany.myapp.singleinstance"
崩溃后锁没释放?不用担心
Mutex 是内核对象,进程异常退出(未执行 ReleaseMutex())时,系统会自动回收所有权。下一次 WaitOne() 调用仍能正常获取,不会永久卡死。
但要注意两点:
-
using块里创建Mutex是安全的,Dispose()会触发Close(),但不等于ReleaseMutex() - 真正释放锁靠的是
ReleaseMutex(),所以务必放在finally或try/finally中 - 如果程序在
Application.Run()后崩溃,finally不会执行,但系统兜底,无需额外处理
别用 Process.GetProcessesByName() 替代
这个方法看着简单,实际问题一堆:
- 竞态条件:A 检查时没发现进程,B 正在启动,A 接着启动 → 两个实例同时跑
- 进程名可伪造,比如复制 exe 并改名就能绕过
- 不同用户会话下,
GetProcessesByName()查不到对方的进程 - UWP/沙盒应用可能根本无权枚举其他进程
它只能作为辅助手段(比如激活已有窗口时找句柄),绝不能当作单实例判定依据。
最易被忽略的一点:Mutex 名称大小写敏感,且不能含非法字符(如 / \ : * ? " |),拼错一个字母就会创建新对象,等于白防。


















