Process.Exited回调未等进程实际退出即触发的问题修复咨询
解决启动进程后Exited回调异常触发的问题
你的代码在处理calc.exe这类应用时出现Exited回调立即执行的情况,原因确实和你推测的一致:Windows 10/11中的计算器是UWP应用,调用Process.Start时实际启动的是代理进程(比如ApplicationFrameHost.exe或explorer.exe),这类代理进程完成UWP主进程的启动后会立刻退出,导致你的Exited事件被触发,但真正的计算器进程仍在运行。
以下是几种可行的解决方案:
1. 追踪代理进程的子进程(通用方案)
通过遍历进程树,找到代理进程启动的实际目标进程,监听它的退出事件:
Process process = new Process(); process.StartInfo = processStartInfo; process.EnableRaisingEvents = true; process.Exited += async (sender, e) => { // 短暂延迟,确保子进程已创建完成 await Task.Delay(1000); // 获取启动进程的所有子进程 var childProcesses = GetChildProcesses(process.Id); // 筛选出实际的目标进程(计算器对应进程名为CalculatorApp) var targetProcess = childProcesses.FirstOrDefault(p => p.ProcessName.Equals("CalculatorApp", StringComparison.OrdinalIgnoreCase)); if (targetProcess != null) { targetProcess.EnableRaisingEvents = true; targetProcess.Exited += (s, args) => { targetProcess.WaitForExit(); RebootMachine(); }; } }; process.Start(); // 获取指定进程的所有子进程(.NET 5+版本) private static List<Process> GetChildProcesses(int parentId) { List<Process> childProcesses = new(); foreach (var p in Process.GetProcesses()) { try { if (p.GetParentProcessId() == parentId) { childProcesses.Add(p); // 递归获取子进程的子进程 childProcesses.AddRange(GetChildProcesses(p.Id)); } } catch { // 进程可能已退出,跳过 continue; } } return childProcesses; }
如果使用.NET Framework(无GetParentProcessId方法),可以用WMI实现父进程ID获取:
private static int GetParentProcessId(int processId) { using var searcher = new ManagementObjectSearcher( $"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {processId}"); foreach (var obj in searcher.Get()) { return Convert.ToInt32(obj["ParentProcessId"]); } return 0; }
2. 直接通过UWP包名启动(最可靠的UWP方案)
针对UWP应用,可以直接通过应用包名启动,避免代理进程干扰:
// 计算器的包名可通过PowerShell命令获取:Get-AppxPackage *calculator* string packageFamilyName = "Microsoft.WindowsCalculator_8wekyb3d8bbwe"; var startInfo = new ProcessStartInfo("explorer.exe", $"shell:AppsFolder\\{packageFamilyName}!App"); var process = Process.Start(startInfo); // 同样需要找到CalculatorApp进程并监听其退出 await Task.Delay(1000); var targetProcess = Process.GetProcessesByName("CalculatorApp").FirstOrDefault(); if (targetProcess != null) { targetProcess.EnableRaisingEvents = true; targetProcess.Exited += (s, args) => { targetProcess.WaitForExit(); RebootMachine(); }; }
3. 轮询检查窗口状态(适合带可见窗口的应用)
如果目标应用有可见窗口,可以通过窗口标题或类名轮询检查窗口是否存在,直到窗口关闭再执行后续操作:
process.Start(); // 轮询检查计算器窗口(标题根据系统语言调整,中文为"计算器") while (IsWindowExists("计算器")) { Thread.Sleep(500); } RebootMachine(); // 辅助方法:检查指定标题的窗口是否存在 [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); private static bool IsWindowExists(string windowTitle) { bool exists = false; EnumWindows((hWnd, lParam) => { var sb = new StringBuilder(256); GetWindowText(hWnd, sb, sb.Capacity); if (sb.ToString().Contains(windowTitle)) { exists = true; return false; } return true; }, IntPtr.Zero); return exists; }
方案对比
- 方案1:通用性强,适用于各类存在代理进程的场景,但需要处理进程创建延迟,可能存在漏检情况。
- 方案2:针对UWP应用最可靠,但需要提前获取目标应用的包名。
- 方案3:无需处理进程父子关系,但依赖窗口标题,多语言环境下需要适配窗口名称。
内容的提问来源于stack exchange,提问作者Anandu Ramachandran
相关产品推荐
相关产品推荐

