.NET控制台应用能否检测系统关机引发的自身终止并上报遥测?
问题分析与解决方案
你遇到的核心问题有两个:一是现有事件处理逻辑不适用关机/重启场景,二是遥测数据因进程退出过快无法完成发送。下面逐一拆解并给出可行方案:
一、现有事件的问题
你订阅的三个事件里,只有AppDomain.ProcessExit有实际作用,另外两个完全不适合当前场景:
Process.Exited:进程已经退出后才触发,此时进程资源已开始回收,TelemetryClient大概率已失效,且没有时间发送遥测。直接移除这个订阅即可。AppDomain.UnhandledException:仅处理未捕获异常,对正常关机、VM重启这类主动终止场景无效。
二、解决遥测发送不及时的问题
TrackEvent默认是异步发送,进程退出时会直接终止后台发送线程,导致遥测丢失。必须在终止事件中强制同步完成发送:
private static void CurrentDomain_ProcessExit(object sender, EventArgs e) { telemetryClient?.TrackEvent("ProcessExit_Initiated"); // 触发遥测发送 telemetryClient?.Flush(); // 等待发送完成,根据网络情况调整时长(建议3-5秒) Task.Delay(3000).Wait(); }
三、捕获VM重启/系统关机事件
你提到的Microsoft.Win32.SystemEvents.SessionEnding是正确方向,但控制台应用本身没有Windows消息循环,无法接收系统广播的关机/注销通知。必须通过隐藏窗体启动消息泵才能捕获该事件,具体实现步骤:
1. 添加依赖
安装System.Windows.Forms NuGet包,用于创建隐藏窗体。
2. 完整实现代码
using System; using System.Threading; using Microsoft.ApplicationInsights; using Microsoft.Win32; using System.Windows.Forms; using System.Threading.Tasks; namespace ConsoleShutdownMonitor { class Program { private static TelemetryClient _telemetryClient; private static Form _hiddenForm; static void Main(string[] args) { _telemetryClient = new TelemetryClient { InstrumentationKey = "你的AppInsights密钥" }; // 订阅进程退出、未处理异常事件 AppDomain.CurrentDomain.ProcessExit += OnProcessExit; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; // 启动隐藏窗体以捕获系统关机/注销事件 StartHiddenFormForSystemEvents(); // 你的核心计算任务逻辑 Console.WriteLine("应用运行中...按Ctrl+C退出"); while (true) { // 模拟计算任务 Thread.Sleep(1000); if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.C && Console.Modifiers == ConsoleModifiers.Control) { break; } } // 退出前清理资源 _hiddenForm?.Close(); _telemetryClient?.Flush(); Task.Delay(3000).Wait(); } private static void StartHiddenFormForSystemEvents() { // 在STA线程创建窗体(WinForms要求) var formThread = new Thread(() => { _hiddenForm = new Form { WindowState = FormWindowState.Minimized, ShowInTaskbar = false }; // 订阅系统会话结束事件(关机、重启、注销) SystemEvents.SessionEnding += OnSessionEnding; // 启动Windows消息循环 Application.Run(_hiddenForm); }); formThread.SetApartmentState(ApartmentState.STA); formThread.Start(); } private static void OnSessionEnding(object sender, SessionEndingEventArgs e) { // 判断终止原因:注销或关机/重启 var reason = e.Reason == SessionEndReasons.Logoff ? "UserLogoff" : "SystemShutdownOrRestart"; _telemetryClient?.TrackEvent($"SessionEnding_{reason}"); _telemetryClient?.Flush(); Task.Delay(3000).Wait(); } private static void OnProcessExit(object sender, EventArgs e) { _telemetryClient?.TrackEvent("ProcessExit_Initiated"); _telemetryClient?.Flush(); Task.Delay(3000).Wait(); } private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) { _telemetryClient?.TrackException((Exception)e.ExceptionObject); _telemetryClient?.Flush(); Task.Delay(3000).Wait(); } } }
3. 关键说明
- 隐藏窗体仅用于接收系统消息,不会对控制台应用的运行产生影响;
SessionEnding事件可以覆盖Azure VM重启、系统关机、用户注销等场景;- 如果是通过任务管理器强制结束进程,系统不会发送任何通知,这种极端场景无法捕获;
- 等待时长可根据Azure网络环境调整,3秒基本能保证遥测发送完成。
内容的提问来源于stack exchange,提问作者Ann L.
相关产品推荐
相关产品推荐

