WPF应用程序捕获由任务管理器发起的强制结束
在聊天划水群刚刚好看到有个水友提出来。首先 我们来整理思路
任务管理器 →“结束任务”通常先给你的窗口发 WM_CLOSE(等价于用户点右上角 ×),如果你及时响应就能走到 WPF 的正常关闭流程(Window.Closing/Application.Exit/AppDomain.ProcessExit 等事件会触发)。
如果用户或系统直接强制终止(TerminateProcess),任何托管/托底事件都不会触发——你无法在进程被硬切掉时执行清理逻辑。
不能 100% 区分“任务管理器点了结束任务”与“用户点 ×”,因为二者都会是 WM_CLOSE 开始。若需区分来源,必须借助外部程序(看门狗)进程(在外部观察你被谁/何时杀掉)。
下面给出能捕获到的所有关闭路径及代码,外加一个Win32 消息钩子
using System; using System.Windows; using Microsoft.Win32; // SystemEvents public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 关机/注销(有 UI 机会) SystemEvents.SessionEnding += (_, args) => { // args.Reason == SessionEndReasons.Logoff/Shutdown Log("SessionEnding: " + args.Reason); // 这里做尽量快的持久化和清理 }; // Dispatcher 未处理异常 this.DispatcherUnhandledException += (_, exArgs) => { Log("DispatcherUnhandledException: " + exArgs.Exception); exArgs.Handled = true; // 避免直接崩溃(按需) }; // 非 UI 线程未处理异常(.NET 4.5+部分场景) AppDomain.CurrentDomain.UnhandledException += (_, exArgs) => { Log("UnhandledException: " + exArgs.ExceptionObject); }; // 进程准备退出(仅“正常路径/有序退出”能走到) AppDomain.CurrentDomain.ProcessExit += (_, __) => { Log("ProcessExit"); // 注意:这里已到 very late 阶段,能做的很有限 }; this.Exit += (_, __) => { Log("Application.Exit"); }; } private static void Log(string s) { // TODO: 写文件/ETW/本地日志;避免 UI 操作 System.Diagnostics.Debug.WriteLine(s); } }
局限:如果任务管理器硬杀(直接 TerminateProcess),ProcessExit/Exit 都不会来。
所以 要挂 Win32 消息钩子
using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; public partial class MainWindow : Window { private HwndSource? _source; public MainWindow() { InitializeComponent(); this.Closing += (_, e) => { // 这里一般是 WM_CLOSE 路径(用户点×、任务管理器尝试关闭) Log("Window.Closing"); // 快速清理/保存;不要耗时太长,避免被系统判定“未响应” }; this.Closed += (_, __) => Log("Window.Closed"); } protected override void OnStartup(EventArgs e) { base.OnStartup(e); var mainWindow = Application.Current.Windows; _source = (HwndSource)PresentationSource.FromVisual(mainWindow )!; _source.AddHook(WndProc); } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_QUERYENDSESSION = 0x0011; // 系统要注销/关机的询问 const int WM_ENDSESSION = 0x0016; // 注销/关机正式开始 const int WM_CLOSE = 0x0010; // 正常关闭窗口(任务管理器结束也用这个) switch (msg) { case WM_QUERYENDSESSION: Log("WM_QUERYENDSESSION"); // 返回 false 会阻止注销/关机(很不推荐);默认让系统继续 break; case WM_ENDSESSION: bool isEnding = wParam != IntPtr.Zero; Log("WM_ENDSESSION isEnding=" + isEnding); break; case WM_CLOSE: Log("WM_CLOSE"); // 和 Window.Closing 等价;不要拦很久--但是任务管理器强制关闭回进来一瞬间它会升级为强杀(这时任何事件都不会来) break; } return IntPtr.Zero; } private static void Log(string s) => System.Diagnostics.Debug.WriteLine("[MainWindow] " + s); }
说明:
任务管理器“结束任务” → 通常先发 WM_CLOSE。如果你不响应或阻塞太久,它会升级为强杀(这时任何事件都不会来)。
关机/注销 → WM_QUERYENDSESSION → WM_ENDSESSION → WM_CLOSE → 退出流程。
能否“知道是任务管理器干的”?
纯粹靠应用内部:不能可靠区分。用户手点 ×、Alt+F4、任务管理器结束,都是 WM_CLOSE。
需要强区分来源:做一个外部看门狗(控制台/服务/另一个进程),用 ManagementEventWatcher/WMI 或轮询监控你的进程。一旦你的进程消失,记录 ExitCode、结合事件日志/安全日志,从外部推断是否为任务管理器/用户操作/崩溃。
基本到此结束。所以挂 Win32 消息钩子也勉强能行,最好加一个监控程序来监控主程序被怎么样干掉。

浙公网安备 33010602011771号