.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
返回1表示允许关机,0表示阻止关机
拿到窗口句柄,可以通过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
.NET 阻止关机机制以及关机前执行业务的更多相关文章
- DOS命令行 定时关机&取消定时关机
命令行关机命令----shutdown Windows XP的关机是由Shutdown.exe程序来控制的,位于Windows\System32文件夹中. 如果你输入"shutd ...
- [转帖]linux screen 命令详解,xshell关掉窗口或者断开连接,查看断开前执行的命令
linux screen 命令详解,xshell关掉窗口或者断开连接,查看断开前执行的命令 https://binwaer.com/post/12.html yun install -y screen ...
- 【java】对象变成垃圾被垃圾回收器gc收回前执行的操作:Object类的protected void finalize() throws Throwable
package 对象被回收前执行的操作; class A{ @Override protected void finalize() throws Throwable { System.out.prin ...
- try中的return语句,在finally前执行还是在finally后执行?
try中有的return语句,也有finally语句,请问finally是否执行,如果执行的话finally在return前执行还是在return后执行? 答案:finally的内容会执行,并且在re ...
- 国庆前执行更新承诺SO交期 FP_SO2SAP
每年9月20日到30号执行以下程序:创建日期为昨天的订单,且承诺交期为10月1到3号,则承诺交期需加7天:创建日期为昨天的订单, 承诺交期为4号到11月1日,承诺交期需加4天 存储过程:FP_SO2S ...
- Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源
深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...
- Spring练习,使用Properties类型注入方式,注入MySQL数据库连接的基本信息,然后使用JDBC方式连接数据库,模拟执行业务代码后释放资源,最后在控制台输出打印结果。
相关 知识 >>> 相关 练习 >>> 实现要求: 使用Properties类型注入方式,注入MySQL数据库连接的基本信息,然后使用JDBC方式连接数据库,模拟执 ...
- Class类文件结构、类加载机制以及字节码执行
一.Class类文件结构 Class类文件严格按照顺序紧凑的排列,由无符号数和表构成,表是由多个无符号数或其他数据项构成的符合数据结构. Class类文件格式按如下顺序排列: 类型 名称 数量 u ...
- 浅论ViewController的加载 -- 解决 viewDidLoad 被提前加载的问题(pushViewController 前执行)
一个ViewController,一般通过init或initWithNibName来加载.二者没有什么不同,init最终还是要调用initWithNibName方法(除非这个ViewControlle ...
- Map/Reduce 工作机制分析 --- 作业的执行流程
前言 从运行我们的 Map/Reduce 程序,到结果的提交,Hadoop 平台其实做了很多事情. 那么 Hadoop 平台到底做了什么事情,让 Map/Reduce 程序可以如此 "轻易& ...
随机推荐
- biancheng-MongoDB教程
目录http://c.biancheng.net/mongodb2/ 1NoSQL是什么2MongoDB是什么3Windows安装MongoDB4Linux安装MongoDB5MacOS安装Mongo ...
- DCT实现水印嵌入与提取(带攻击)
问题: 想要用DCT技术,在Matlib上实现水印的隐藏和提取(带GUI界面),且加上一些攻击(噪声.旋转.裁剪),以及用NC值评判! 流程 选择载体 [filename,pathname]=uige ...
- 常用Maven命令
一.常用命令 1.1 打包 mvn clean package -DskipTests 指定环境 mvn clean install -Dmaven.test.skip=true -Pprod-tx ...
- HT-014 Div3 扫雷 题解 [ 绿 ] [ 二维差分 ]
分析 观察到是曼哈顿距离 \(\le r\) 的范围可以扫到,联想到如下图形: 左边是 \(r=1\) 可以扫到的范围,右边是 \(r=2\) 可以扫到的范围. 于是,我们只要对这样的图形在 \(10 ...
- 如何正确配置 .gitignore 以忽略特定文件夹下的文件(除指定子文件夹外)
在使用 Git 进行版本控制时,.gitignore 文件是一个非常有用的工具,可以帮助我们忽略不需要跟踪的文件或文件夹.然而,有时我们需要忽略某个文件夹下的所有内容,但保留其中的某个子文件夹.本文将 ...
- kubesphere应用系列(二)部署有状态服务redis
前言 在 Kubernetes 中,服务(Service)可以被分为有状态服务和无状态服务,个人认为的区别: 无状态服务是指不依赖于任何持久化状态的服务.它们通常是将请求处理为独立.无关的事务,并且在 ...
- [THUSC2015] 异或运算 题解
学到新思路了:求解 \(k\) 大值时,可以将所有元素放一块一起跑. 考虑到 \(n,q\) 奇小无匹,我们便可以制造一个 \(O(qn\log V)\) 的代码. 那么对于我们不想在时间复杂度中出现 ...
- JavaScript 之 高级程序设计 基础篇 (一)
导读 此篇文章为作者拜读JavaScrpit 第四版(红宝石)的笔记内容.适用于有经验的程序员阅读:作者 java开发出身.在之前前后端不分离的时代 使用esayUI JQuery的时代 经常写 js ...
- [vue系列]-vue+vue-i18n+elementUI 国际化
前言 vue+vue-i18n实现多语言 本文主要内容 安装 多语言配置 element 内置语言国际化 踩到的坑以及解决方案 安装 npm install vue-i18n 配置 1.i18n.js ...
- 不上苹果的app store,安装ios应用最简单的方法
不上架appstore,安装app有两种方法,一种是使用企业类型的苹果开发者账号的In house类型的证书和证书profile文件打包,一种是使用个人/公司类型的苹果开发者账号的ad hoc类型的证 ...