You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在用户取消关机后保持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:实现应用自守护逻辑

如果不想改为服务,可通过以下方式实现自守护:

  1. 修改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");
        }
    }
    
  2. 编写独立监控逻辑
    用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秒是为了给系统足够时间完成关机取消流程,再检查应用是否存活。

  3. 修复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:12:21