Windows服务安装后重复启动SysDemConnect实例的解决问询
SysDemConnect Windows服务重复启动实例问题
背景
- 主应用为SysDemConnect,用户通过桌面快捷方式启动
- 应用内置「安装Windows服务」按钮,该服务用于系统重启时自动启动SysDemConnect,应对断电/意外关机场景
- 服务设置为安装后自动启动,此为预期必需行为
当前问题
安装服务后,服务启动的同时会额外启动一个SysDemConnect实例,但此时用户已经打开了该应用,不符合「已有实例运行时不启动新实例」的预期。
初始OnStart代码
protected override void OnStart(string[] args) { try { Process cmd = new Process(); string filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SysDem.Connect.exe"); cmd.StartInfo.FileName = filename; cmd.StartInfo.WindowStyle = ProcessWindowStyle.Normal; cmd.StartInfo.Arguments = null; cmd.Start(); processId = cmd.Id; sessionID = cmd.SessionId; } catch (Exception ex) { ConnectServiceLogger.WriteToConnectServiceLog(ex.Message); } }
尝试过的方案及问题
修改后的代码尝试检测进程是否存在,但系统重启时OnStart()方法看似未执行(服务仍处于运行状态):
protected override void OnStart(string[] args) { try { Process[] sysdemConnectProcess = Process.GetProcessesByName("SysDem.Connect"); bool isRunning = sysdemConnectProcess.Length > 0; if (!isRunning) { Process cmd = new Process(); string filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SysDem.Connect.exe"); cmd.StartInfo.FileName = filename; cmd.StartInfo.WindowStyle = ProcessWindowStyle.Normal; cmd.StartInfo.Arguments = null; cmd.Start(); processId = cmd.Id; sessionID = cmd.SessionId; } } catch (Exception ex) { ConnectServiceLogger.WriteToConnectServiceLog(ex.Message); } }
解决思路
1. 修正进程检测的会话隔离问题
Windows服务默认运行在Session 0,而用户桌面启动的应用在用户会话(如Session 1),Vista及以后的系统存在Session 0隔离机制,导致服务无法直接读取用户会话的GUI进程信息。原代码用Process.GetProcessesByName可能无法获取到用户会话中的实例,导致错误启动新进程。
可以通过WMI查询所有会话的进程来解决:
using System.Management; private bool IsAppRunning() { var processQuery = new SelectQuery("SELECT * FROM Win32_Process WHERE Name = 'SysDem.Connect.exe'"); using (var searcher = new ManagementObjectSearcher(processQuery)) { foreach (var processObj in searcher.Get()) { // 只要存在该进程,无论哪个会话都返回true return true; // 若仅需判断用户会话实例,可添加: // int sessionId = Convert.ToInt32(processObj["SessionId"]); // if (sessionId != 0) return true; } } return false; }
注意:需添加System.Management程序集引用,且服务需具备读取WMI的权限。
2. 用全局互斥锁实现单实例(更可靠)
在SysDemConnect主程序入口添加全局互斥锁,不管是服务启动还是用户启动,都能检测到已有实例并自动退出,从根源避免重复实例:
static void Main() { // 全局命名空间的互斥锁,跨会话生效 using (var instanceMutex = new Mutex(true, "Global\\SysDemConnect_SingleInstance", out bool isNewInstance)) { if (isNewInstance) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } else { // 已有实例运行,可弹窗提示或直接退出 MessageBox.Show("SysDemConnect已在运行"); return; } } }
这种方式无需在服务端做复杂检测,服务可保留原启动逻辑,新启动的进程会自行判断并退出。
3. 排查OnStart未执行的假象
修改后的代码重启时看似OnStart未执行,实际可能是:
- 进程检测逻辑导致服务未启动进程,看起来像没执行:在
try块开头添加日志,确认OnStart是否触发:ConnectServiceLogger.WriteToConnectServiceLog("OnStart 方法已触发"); - 存在未捕获的异常:检查日志是否记录完整错误,比如WMI操作的异常是否被正确捕获
- 进程名称匹配错误:
GetProcessesByName的参数是进程的友好名称(不带.exe),可通过任务管理器确认进程的实际名称,或改用路径匹配:Process.GetProcesses().Any(p => p.MainModule?.FileName.EndsWith("SysDem.Connect.exe") == true)
4. 服务启动参数区分
服务启动SysDemConnect时传递专属参数(如-service),应用程序收到该参数时,若检测到已有实例则直接退出,不弹出UI:
// 服务端OnStart修改启动参数 cmd.StartInfo.Arguments = "-service"; // 应用程序Main方法处理参数 static void Main(string[] args) { bool isServiceLaunch = args.Contains("-service"); using (var mutex = new Mutex(true, "Global\\SysDemConnectInstanceMutex", out bool createdNew)) { if (createdNew) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } else { if (isServiceLaunch) { // 服务启动的实例直接退出,不提示 return; } MessageBox.Show("SysDemConnect已在运行"); } } }
内容的提问来源于stack exchange,提问作者Lech Krawczyk
相关产品推荐
相关产品推荐

