.NET 阻止关机机制以及关机前执行业务

本文主要介绍Windows在关闭时,如何正确、可靠的阻止系统关机以及关机前执行相应业务。因有一些场景需要在关机/重启前执行业务逻辑,确保下次开机时数据的一致性以及可靠性。

统一整理,以下是实现这一需求的几种方法,

1. Windows消息Hook勾子

复制代码
 1     public MainWindow()
 2     {
 3         InitializeComponent();
 4         SourceInitialized += OnSourceInitialized;
 5     }
 6     private void OnSourceInitialized(object sender, EventArgs e)
 7     {
 8         var source = PresentationSource.FromVisual(this) as HwndSource;
 9         source?.AddHook(WndProc);
10     }
11     const int WM_QUERYENDSESSION = 0x11;
12     const int WM_ENDSESSION = 0x16;
13     private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
14     {
15         if (msg == WM_QUERYENDSESSION)
16         {
17             var currentMainWindow = Application.Current.MainWindow;
18             var handle = new WindowInteropHelper(currentMainWindow).Handle;
19             ShutdownBlockReasonDestroy(handle);
20             ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
21             // 在这里执行你的业务逻辑
22             bool canShutdown = PerformShutdownWork();
23 
24             // 返回0表示阻止关机,1表示允许关机
25             handled = true;
26             return canShutdown ? (IntPtr)1 : (IntPtr)0;
27         }
28         return IntPtr.Zero;
29     }
30 
31     private bool PerformShutdownWork()
32     {
33         Thread.Sleep(TimeSpan.FromSeconds(10));
34         return true;
35     }
36 
37     [DllImport("user32.dll")]
38     private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
39     [DllImport("user32.dll")]
40     private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);

通过Hook循环windows窗口消息,WndProc接收到WM_QUERYENDSESSION时表示有关机调用,详细的可以查看官网文档:(WinUser.h) WM_QUERYENDSESSION消息 - Win32 apps | Microsoft Learn

WndProc返回1表示允许关机,0表示阻止关机

拿到每个应用的关机确认结果,再广播WM_ENDSESSION、执行真正的关闭

拿到窗口句柄,可以通过ShutdownBlockReasonCreate设置阻止关机原因,ShutdownBlockReasonDestroy清理关机阻止原因,详见:ShutdownBlockReasonCreate 函数 (winuser.h) - Win32 apps | Microsoft Learn

阻止进行中的效果:

2.Win32系统事件SystemEvents

复制代码
 1     public partial class App : Application
 2     {
 3         public App()
 4         {
 5             SystemEvents.SessionEnding += SystemEvents_SessionEnding;
 6             Application.Current.Exit += Current_Exit;
 7         }
 8 
 9         private void Current_Exit(object sender, ExitEventArgs e)
10         {
11             SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
12         }
13 
14         private async void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
15         {
16             if (e.Reason == SessionEndReasons.SystemShutdown)
17             {
18                 var currentMainWindow = Application.Current.MainWindow;
19                 var handle = new WindowInteropHelper(currentMainWindow).Handle;
20                 ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
21                 var canShutDown = PerformShutdownWork();
22                 ShutdownBlockReasonDestroy(handle);
23                 e.Cancel = !canShutDown;
24             }
25         }
26         
27         private bool PerformShutdownWork()
28         {
29             Thread.Sleep(TimeSpan.FromSeconds(20));
30             return true;
31         }
32         
33         [DllImport("user32.dll")]
34         private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
35 
36         [DllImport("user32.dll")]
37         private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
38     }

也可以监听SessionEndReasons.SystemShutdown关机事件。实际上也是基于消息机制,但封装了细节、提供更高级抽象

这里e.Cancel,true表示阻止关机

因为需要设置关机阻止原因,SystemEvents.SessionEnding也是要依赖窗口的。当然,因为依赖窗口会导致勾子失败,下面我们会聊

阻止关机失败的一些原因

以上俩种方式,均可以实现阻止系统关机以及关机前执行相应业务。但Hook勾子也可能失效,不能正常执行完你的业务逻辑

1. 关机勾子只支持UI线程,不支持异步调用

如果有业务使用了async,需要业务上下游所有调用链条均添加.ConfigureAwait,不切换上下文。否则系统不会等待、往下直接关机了

2. 窗口Hide,导致勾子失效

ShutdownBlockReasonCreate 函数需要窗口处于活动状态,窗口Hide之后肯定是不行了。那如何解决呢?

有俩个方法,

首先可以替换Hide为Visibility,这个我验证是可以的。不调用Hide,只设置Visibility就行了。ShutdownBlockReasonCreate 设置关机原因,就不受窗口Hide影响了。验证ok

第二个,因为根源还是设置关机阻止Resion,那是否可以提前去设置呢?不要等窗口Hide之后再去设置或者关机时去设置...

所以,完全可以在主窗口内提前设置:

复制代码
 1     public partial class MainWindow : Window
 2     {
 3         public MainWindow()
 4         {
 5             InitializeComponent();
 6             Loaded += MainWindow_Loaded;
 7         }
 8         private void MainWindow_Loaded(object sender, RoutedEventArgs e)
 9         {
10             Loaded -= MainWindow_Loaded;
11             var currentMainWindow = Application.Current.MainWindow;
12             var handle = new WindowInteropHelper(currentMainWindow).Handle;
13             ShutdownBlockReasonDestroy(handle);
14             ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
15 
16             //窗口Hide,并不影响上面的ShutdownBlockReasonDestroy
17             Hide();
18         }
19         [DllImport("user32.dll")]
20         private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
21         [DllImport("user32.dll")]
22         private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
23     }

上面代码也注释了,设置完关机原因、再去Hide。关机事件触发后,是能正常保障阻止机制的。验证ok

这里也推荐大家使用SystemEvents.SessionEnding方式,关机阻止原因与关机时的执行业务可以分离开来,不受MainWindow窗口的入口限定

3.360安全卫士、QQ电脑管家等优化软件,可能会优化此类关机阻止机制

这些安全软件关机时可能直接强杀,用来提升关机/重启速度。个人是不建议使用这些安全软件的,都是流氓。。。

关机阻止超时的情况及建议

关机重启是有时间限制的,我试了下,在设置关机阻止原因情况下,应用最多只能持续60秒左右

超过60s后系统取消关机、回登录界面,然后当前阻止的进程会在执行完Hook后自动关闭(其它进程不会关闭)

如果Hook勾子内我们执行的业务太过耗时,可能不一定能执行完。建议只执行更少、必须的业务

另外,关机时应用关闭是有顺序的。如果想提高一点应用关机时应用能应对的时间,略微提升关机前业务执行的成功率,可以对进程添加关闭优先级:

复制代码
1         public MainWindow()
2         {
3             InitializeComponent();
4 
5             // 在应用程序启动时调用
6             SetProcessShutdownParameters(0x4FF, 0);
7         }
8         [DllImport("kernel32.dll")]
9         static extern bool SetProcessShutdownParameters(uint dwLevel, uint dwFlags);

0x100表示最低优先级,确保你的程序最先被关闭

0x4FF表示最高优先级,确保你的程序最后被关闭

详细的参考文档: SetProcessShutdownParameters 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

相关推荐
wmq1633 个月前
口碑很好的国产LDO芯片,有哪些?
电源·选型·ldo
WPG大大通4 个月前
基于DIODES AP43781+PI3USB31531+PI3DPX1207C的USB-C PD& Video 之全功能显示器连接端口方案
c语言·开发语言·计算机外设·开发板·电源·大大通
唐宋元明清21884 个月前
.NET 阻止系统睡眠/息屏
windows·电源
7yewh4 个月前
嵌入式硬件实战提升篇(三)商用量产电源设计方案 三路电源输入设计 电源管理 多输入供电自动管理 DCDC降压
嵌入式硬件·硬件架构·soc·pcb工艺·电源·dcdc·原理图设计
小齿轮lsl5 个月前
半波整流器原理
单片机·嵌入式硬件·学习·电力电子·电源·电源硬件
DCTANT8 个月前
【原创】解决七彩虹显卡开启turbo模式后,电脑开机蜂鸣器1长3短,自检码卡AA的问题
电脑·显卡·电源·供电不足·一长三短
TOWE technology10 个月前
案例分享:同为科技与军工项目合作
科技·snmp·电源·智能·军工
伊织code10 个月前
Apple - IOKit Fundamentals
macos·i/o·开发·驱动·电源·driver·iokit
9亿少女的噩梦1 年前
南京观海微电子---电源,从微观角度观看电功率是怎么产生
电源