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

打印 上一主题 下一主题

主题 1827|帖子 1827|积分 5481

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本文主要先容Windows在关闭时,如何正确、可靠的阻止系统关机以及关机前执行相应业务。因有一些场景必要在关机/重启前执行业务逻辑,确保下次开机时数据的一致性以及可靠性。
同一整理,以下是实现这一需求的几种方法,
1. Windows消息Hook勾子
  1. 1     public MainWindow()
  2. 2     {
  3. 3         InitializeComponent();
  4. 4         SourceInitialized += OnSourceInitialized;
  5. 5     }
  6. 6     private void OnSourceInitialized(object sender, EventArgs e)
  7. 7     {
  8. 8         var source = PresentationSource.FromVisual(this) as HwndSource;
  9. 9         source?.AddHook(WndProc);
  10. 10     }
  11. 11     const int WM_QUERYENDSESSION = 0x11;
  12. 12     const int WM_ENDSESSION = 0x16;
  13. 13     private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  14. 14     {
  15. 15         if (msg == WM_QUERYENDSESSION)
  16. 16         {
  17. 17             var currentMainWindow = Application.Current.MainWindow;
  18. 18             var handle = new WindowInteropHelper(currentMainWindow).Handle;
  19. 19             ShutdownBlockReasonDestroy(handle);
  20. 20             ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
  21. 21             // 在这里执行你的业务逻辑
  22. 22             bool canShutdown = PerformShutdownWork();
  23. 23
  24. 24             // 返回0表示阻止关机,1表示允许关机
  25. 25             handled = true;
  26. 26             return canShutdown ? (IntPtr)1 : (IntPtr)0;
  27. 27         }
  28. 28         return IntPtr.Zero;
  29. 29     }
  30. 30
  31. 31     private bool PerformShutdownWork()
  32. 32     {
  33. 33         Thread.Sleep(TimeSpan.FromSeconds(10));
  34. 34         return true;
  35. 35     }
  36. 36
  37. 37     [DllImport("user32.dll")]
  38. 38     private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
  39. 39     [DllImport("user32.dll")]
  40. 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. 1     public partial class App : Application
  2. 2     {
  3. 3         public App()
  4. 4         {
  5. 5             SystemEvents.SessionEnding += SystemEvents_SessionEnding;
  6. 6             Application.Current.Exit += Current_Exit;
  7. 7         }
  8. 8
  9. 9         private void Current_Exit(object sender, ExitEventArgs e)
  10. 10         {
  11. 11             SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
  12. 12         }
  13. 13
  14. 14         private async void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
  15. 15         {
  16. 16             if (e.Reason == SessionEndReasons.SystemShutdown)
  17. 17             {
  18. 18                 var currentMainWindow = Application.Current.MainWindow;
  19. 19                 var handle = new WindowInteropHelper(currentMainWindow).Handle;
  20. 20                 ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
  21. 21                 var canShutDown = PerformShutdownWork();
  22. 22                 ShutdownBlockReasonDestroy(handle);
  23. 23                 e.Cancel = !canShutDown;
  24. 24             }
  25. 25         }
  26. 26         
  27. 27         private bool PerformShutdownWork()
  28. 28         {
  29. 29             Thread.Sleep(TimeSpan.FromSeconds(20));
  30. 30             return true;
  31. 31         }
  32. 32         
  33. 33         [DllImport("user32.dll")]
  34. 34         private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
  35. 35
  36. 36         [DllImport("user32.dll")]
  37. 37         private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
  38. 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. 1     public partial class MainWindow : Window
  2. 2     {
  3. 3         public MainWindow()
  4. 4         {
  5. 5             InitializeComponent();
  6. 6             Loaded += MainWindow_Loaded;
  7. 7         }
  8. 8         private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  9. 9         {
  10. 10             Loaded -= MainWindow_Loaded;
  11. 11             var currentMainWindow = Application.Current.MainWindow;
  12. 12             var handle = new WindowInteropHelper(currentMainWindow).Handle;
  13. 13             ShutdownBlockReasonDestroy(handle);
  14. 14             ShutdownBlockReasonCreate(handle, "应用保存数据中,请等待...");
  15. 15
  16. 16             //窗口Hide,并不影响上面的ShutdownBlockReasonDestroy
  17. 17             Hide();
  18. 18         }
  19. 19         [DllImport("user32.dll")]
  20. 20         private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
  21. 21         [DllImport("user32.dll")]
  22. 22         private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
  23. 23     }
复制代码
上面代码也解释了,设置完关机原因、再去Hide。关机事件触发后,是能正常保障阻止机制的。验证ok
这里也推荐大家使用SystemEvents.SessionEnding方式,关机阻止原因与关机时的执行业务可以分离开来,不受MainWindow窗口的入口限定
3.360安全卫士、QQ电脑管家等优化软件,可能会优化此类关机阻止机制
这些安全软件关机时可能直接强杀,用来提拔关机/重启速度。个人是不建议使用这些安全软件的,都是流氓。。。
关机阻止超时的情况及建议

关机重启是有时间限定的,我试了下,在设置关机阻止原因情况下,应用最多只能一连60秒左右
超过60s后系统取消关机、回登录界面,然后当前阻止的进程会在执行完Hook后自动关闭(其它进程不会关闭)
如果Hook勾子内我们执行的业务太过耗时,可能不一定能执行完。建议只执行更少、必须的业务
另外,关机时应用关闭是有次序的。如果想进步一点应用关机时应用能应对的时间,略微提拔关机前业务执行的乐成率,可以对进程添加关闭优先级:
  1. 1         public MainWindow()
  2. 2         {
  3. 3             InitializeComponent();
  4. 4
  5. 5             // 在应用程序启动时调用
  6. 6             SetProcessShutdownParameters(0x4FF, 0);
  7. 7         }
  8. 8         [DllImport("kernel32.dll")]
  9. 9         static extern bool SetProcessShutdownParameters(uint dwLevel, uint dwFlags);
复制代码
0x100表示最低优先级,确保你的步调最先被关闭
0x4FF表示最高优先级,确保你的步调最后被关闭
详细的参考文档: SetProcessShutdownParameters 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn
出处:http://www.cnblogs.com/kybs0/让学习成为习惯,假设来日诰日就有重大机会等着你,你准备好了么本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

熊熊出没

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表