如何在用户取消关机后保持C# WPF后台应用持续运行
问题描述
我有一个以后台进程运行的C# WPF应用,用于统计用户休息时长——通过锁屏及连续解锁行为判定休息状态。当休息时长超过设定分钟数时,会通过UI获取休息原因。
需求是该应用需持续在后台运行,但遇到以下场景时失效:用户点击关机后,因其他应用(如记事本)存在未保存更改导致关机被终止,用户取消关机后,我的应用不再运行。
我尝试了以下方案但均未成功:
- 在
SystemEvents_SessionEnding事件中调用一个延迟5分钟后重启应用的.bat文件 - 使用PowerShell脚本实现上述相同逻辑
相关代码
C# 代码
private void Application_Startup(object sender, StartupEventArgs e) { SystemEvents.SessionEnding += SystemEvents_SessionEnding; Application.Current.Exit += Application_Exit; //and some mutex codes are here } private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) { try { if (e.Reason == SessionEndReasons.Logoff || e.Reason == SessionEndReasons.SystemShutdown) { if(SharedResource.Instance.IsSystemLocked ==false) { App_InsertLockDetais(false, null, null); //本地SQLite数据库插入操作 } } if(e.Reason == SessionEndReasons.SystemShutdown) { ReInitiateApp(); } } catch (Exception ex) { ErrorHandler.HandleException(ex, "SystemEvents_SessionEnding"); } } private void ReInitiateApp() { try { string appPath = AppDomain.CurrentDomain.BaseDirectory; string batFilePath = Path.Combine(appPath, "phoenix.bat"); var process = new Process { StartInfo = new ProcessStartInfo { FileName = batFilePath, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); } catch (Exception ex) { ErrorHandler.HandleException(ex, "ReInitiateApp"); } } private void Application_Exit(object sender, ExitEventArgs e) { SystemEvents.SessionSwitch -= SystemEvents_SessionSwitch; SystemEvents.SessionEnding -= SystemEvents_SessionEnding; _mutexuser?.ReleaseMutex(); }
phoenix.bat 文件内容
@echo off timeout /t 600 /nobreak start "" "C:\ProgramData\MyExeFolder\MyExe.exe"
解决方案
问题根源
系统触发SessionEnding事件时,无论最终关机是否成功,WPF应用都会被强制终止——因为系统发起关机流程时会先通知所有应用退出,即便后续关机被取消,已退出的应用不会自动恢复。而你通过ReInitiateApp启动的bat脚本,因依附于原应用进程,会随原进程终止而无法正常执行。
可行方案
方案1:改用Windows服务托管核心逻辑
将统计休息时长的核心逻辑剥离为Windows服务,服务不受用户会话关机取消、注销等操作影响,始终在后台运行。WPF UI作为独立客户端,通过IPC(如命名管道、WCF)与服务通信,负责展示和交互。
优势:服务本身为后台持续运行设计,从根本上解决应用意外终止的问题。
方案2:实现应用自守护逻辑
如果不想改为服务,可通过以下方式实现自守护:
修改
SessionEnding事件处理
取消系统退出通知,让应用继续运行,同时启动独立监控进程检测应用存活状态:private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) { try { if (e.Reason == SessionEndReasons.Logoff || e.Reason == SessionEndReasons.SystemShutdown) { if(SharedResource.Instance.IsSystemLocked == false) { App_InsertLockDetais(false, null, null); } } if(e.Reason == SessionEndReasons.SystemShutdown) { // 阻止系统强制终止应用 e.Cancel = true; // 启动独立监控进程 StartMonitorProcess(); } } catch (Exception ex) { ErrorHandler.HandleException(ex, "SystemEvents_SessionEnding"); } }编写独立监控逻辑
用PowerShell实现延迟检查并重启应用,确保监控进程与原应用完全独立:private void StartMonitorProcess() { string appName = Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName); string targetExe = "C:\\ProgramData\\MyExeFolder\\MyExe.exe"; string script = $@" Start-Sleep -Seconds 30 if (-not (Get-Process -Name {appName} -ErrorAction SilentlyContinue)) {{ Start-Process '{targetExe}' }} "; var processInfo = new ProcessStartInfo { FileName = "powershell.exe", Arguments = $"-Command \"{script}\"", UseShellExecute = true, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden }; Process.Start(processInfo); }延迟30秒是为了给系统足够时间完成关机取消流程,再检查应用是否存活。
修复bat脚本启动方式
若坚持使用bat脚本,需让脚本由系统shell独立启动,避免依附原进程:private void ReInitiateApp() { try { string appPath = AppDomain.CurrentDomain.BaseDirectory; string batFilePath = Path.Combine(appPath, "phoenix.bat"); var process = new Process { StartInfo = new ProcessStartInfo { FileName = batFilePath, UseShellExecute = true, // 设为true让系统独立启动脚本 CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden } }; process.Start(); } catch (Exception ex) { ErrorHandler.HandleException(ex, "ReInitiateApp"); } }
额外建议
- 添加崩溃重启机制:应用启动时检查是否存在异常退出标记,若有则自动重启。
- 结合
WM_QUERYENDSESSION窗口消息:WPF应用可通过重写Window.WndProc捕获该消息,提升系统关机通知的可靠性。
内容的提问来源于stack exchange,提问作者Alphonse
相关产品推荐
相关产品推荐

