DOTNET阻止关机机制及关机前业务执行方法
笔记哥 /
04-13 /
9点赞 /
0评论 /
231阅读
本文主要介绍Windows在关闭时,如何正确、可靠的阻止系统关机以及关机前执行相应业务。因有一些场景需要在关机/重启前执行业务逻辑,确保下次开机时数据的一致性以及可靠性。
统一整理,以下是实现这一需求的几种方法,
### 1. Windows消息Hook勾子
```csharp
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
```csharp
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之后再去设置或者关机时去设置...
所以,完全可以在主窗口内提前设置:
```csharp
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勾子内我们执行的业务太过耗时,可能不一定能执行完。建议只执行更少、必须的业务
另外,关机时应用关闭是有顺序的。如果想提高一点应用关机时应用能应对的时间,略微提升关机前业务执行的成功率,可以对进程添加关闭优先级:
```csharp
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
本文来自投稿,不代表本站立场,如若转载,请注明出处:http//www.knowhub.vip/share/2/2188
- 热门的技术博文分享
- 1 . ESP实现Web服务器
- 2 . 从零到一:打造高效的金仓社区 API 集成到 MCP 服务方案
- 3 . 使用C#构建一个同时问多个LLM并总结的小工具
- 4 . .NET 原生驾驭 AI 新基建实战系列Milvus ── 大规模 AI 应用的向量数据库首选
- 5 . 在Avalonia/C#中使用依赖注入过程记录
- 6 . [设计模式/Java] 设计模式之工厂方法模式
- 7 . 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
- 8 . SQL 中的各种连接 JOIN 的区别总结!
- 9 . JavaScript 中防抖和节流的多种实现方式及应用场景
- 10 . SaltStack 远程命令执行中文乱码问题
- 11 . 推荐10个 DeepSeek 神级提示词,建议搜藏起来使用
- 12 . C#基础:枚举、数组、类型、函数等解析
- 13 . VMware平台的Ubuntu部署完全分布式Hadoop环境
- 14 . C# 多项目打包时如何将项目引用转为包依赖
- 15 . Chrome 135 版本开发者工具(DevTools)更新内容
- 16 . 从零创建npm依赖,只需执行一条命令
- 17 . 关于 Newtonsoft.Json 和 System.Text.Json 混用导致的的序列化不识别的问题
- 18 . 大模型微调实战之训练数据集准备的艺术与科学
- 19 . Windows快速安装MongoDB之Mongo实战
- 20 . 探索 C# 14 新功能:实用特性为编程带来便利
- 相关联分享
- DOTNET阻止关机机制及关机前业务执行方法