国产高清在线免费观看-国产高清在线免费无码-国产高清在线男人的天堂-国产高清在线视频-国产高清在线视频精品视频-国产高清在线视频伊甸园

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

.NET 阻止Windows關(guān)機(jī)以及阻止失敗的一些原因

freeflydom
2025年4月14日 8:32 本文熱度 310

本文主要介紹Windows在關(guān)閉時(shí),如何正確、可靠的阻止系統(tǒng)關(guān)機(jī)以及關(guān)機(jī)前執(zhí)行相應(yīng)業(yè)務(wù)

Windows關(guān)機(jī),默認(rèn)會(huì)給應(yīng)用幾s的關(guān)閉時(shí)間,但有一些場景需要在關(guān)機(jī)/重啟前執(zhí)行更長時(shí)間的業(yè)務(wù)邏輯,確保下次開機(jī)時(shí)數(shù)據(jù)的一致性以及可靠性。我司目前業(yè)務(wù)也用到關(guān)機(jī)阻止,但這塊之前并未梳理清楚,依賴BUG編程,導(dǎo)致后續(xù)維護(hù)項(xiàng)目時(shí)又會(huì)導(dǎo)致關(guān)機(jī)這塊出現(xiàn)新問題。

統(tǒng)一整理,以下是實(shí)現(xiàn)這一需求的幾種方法,

1. Windows消息Hook勾子

 1     public MainWindow()
 2     {
 3         InitializeComponent();
 4         Loaded += OnLoaded;
 5     }
 6 
 7     private void OnLoaded(object sender, RoutedEventArgs e)
 8     {
 9         Loaded -= OnLoaded;
10         var source = PresentationSource.FromVisual(this) as HwndSource;
11         source?.AddHook(WndProc);
12     }
13     const int WM_QUERYENDSESSION = 0x11;
14     const int WM_ENDSESSION = 0x16;
15     private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
16     {
17         if (msg == WM_QUERYENDSESSION)
18         {
19             var handle = new WindowInteropHelper(this).Handle;
20             ShutdownBlockReasonCreate(handle, "應(yīng)用保存數(shù)據(jù)中,請等待...");
21             // 可以在這里執(zhí)行你的業(yè)務(wù)邏輯
22             bool executeSuccess = ExecuteShutdownWork();
23             // 返回0表示阻止關(guān)機(jī),1表示允許關(guān)機(jī)
24             handled = true;
25             return executeSuccess ? (IntPtr)1 : (IntPtr)0;
26         }
27         return (IntPtr)1;
28     }
29 
30     private bool ExecuteShutdownWork()
31     {
32         Thread.Sleep(TimeSpan.FromSeconds(20));
33         //測試,默認(rèn)返回操作失敗
34         return false;
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循環(huán)windows窗口消息,WndProc接收到WM_QUERYENDSESSION時(shí)表示有關(guān)機(jī)調(diào)用,詳細(xì)的可以查看官網(wǎng)文檔:(WinUser.h) WM_QUERYENDSESSION消息 - Win32 apps | Microsoft Learn

WndProc返回1表示業(yè)務(wù)正常,0表示取消、阻止關(guān)機(jī)。這里我們默認(rèn)操作失敗,阻止關(guān)機(jī)

拿到每個(gè)應(yīng)用的關(guān)機(jī)確認(rèn)結(jié)果,再廣播WM_ENDSESSION、執(zhí)行真正的關(guān)閉

拿到窗口句柄,可以通過ShutdownBlockReasonCreate設(shè)置阻止關(guān)機(jī)原因,ShutdownBlockReasonDestroy清理關(guān)機(jī)阻止原因,詳見:ShutdownBlockReasonCreate 函數(shù) (winuser.h) - Win32 apps | Microsoft Learn

阻止進(jìn)行中的效果:

上面demo運(yùn)行20s之后,系統(tǒng)會(huì)退出關(guān)機(jī)狀態(tài)、返回登錄界面

2.Win32系統(tǒng)事件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         private void Current_Exit(object sender, ExitEventArgs e)
 9         {
10             SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
11         }
12         private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
13         {
14             if (e.Reason == SessionEndReasons.SystemShutdown)
15             {
16                 var handle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
17                 ShutdownBlockReasonDestroy(handle);
18                 ShutdownBlockReasonCreate(handle, "應(yīng)用保存數(shù)據(jù)中,請等待...");
19 
20                 var executeSuccess = ExecuteShutdownWork();
21                 e.Cancel = !executeSuccess;
22             }
23         }
24         private bool ExecuteShutdownWork()
25         {
26             //Test
27             Thread.Sleep(TimeSpan.FromSeconds(200));
28             return false;
29             try
30             {
31                 // XXX
32                 return true;
33             }
34             catch (Exception e)
35             {
36                 return false;
37             }
38         }
39 
40         [DllImport("user32.dll")]
41         private static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
42         [DllImport("user32.dll")]
43         private static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
44     }

也可以監(jiān)聽SessionEndReasons.SystemShutdown關(guān)機(jī)事件。實(shí)際上也是基于消息機(jī)制,但封裝了細(xì)節(jié)、提供更高級(jí)抽象

這里e.Cancel,false表示不取消用戶請求、不關(guān)機(jī),true表示取消用戶請求、阻止關(guān)機(jī)

因?yàn)樾枰O(shè)置關(guān)機(jī)阻止原因,SystemEvents.SessionEnding也是要依賴窗口的。當(dāng)然,因?yàn)橐蕾嚧翱跁?huì)導(dǎo)致勾子失敗,下面我們會(huì)聊

阻止關(guān)機(jī)失敗的一些原因

以上倆種方式,均可以實(shí)現(xiàn)阻止系統(tǒng)關(guān)機(jī)以及關(guān)機(jī)前執(zhí)行相應(yīng)業(yè)務(wù)。但Hook勾子也可能失效,不能正常執(zhí)行完你的業(yè)務(wù)邏輯

1. 關(guān)機(jī)勾子只支持UI線程,不支持異步調(diào)用

第一種,SessionEnding事件被修改為了async void

第二種,業(yè)務(wù)內(nèi)部調(diào)用了異步方法,通過.Result、.Wait()期望等待完成。但其實(shí)內(nèi)部并沒有完全添加.ConfigureAwait,這也會(huì)導(dǎo)致關(guān)機(jī)阻止失敗。

我司業(yè)務(wù)就遇到了第二個(gè)問題,在消息循環(huán)WndProc之后,添加了上報(bào)后臺(tái)日志。為了滿足勾子只支持同步調(diào)用,日志模塊就使用了.Result轉(zhuǎn)為同步方法:

對于這類限定UI線程同步執(zhí)行場景,我的解決辦法是,減少邏輯、去除發(fā)送后臺(tái)日志。

另外,如果下面業(yè)務(wù)真的需要使用async,需要業(yè)務(wù)上下游所有調(diào)用鏈條均添加.ConfigureAwait,不切換上下文。否則系統(tǒng)不會(huì)等待、往下直接關(guān)機(jī)了

2. 窗口Hide,導(dǎo)致勾子失效

一些窗口啟動(dòng)后,需要立即Hide窗口:

1     public MainWindow()
2     {
3         InitializeComponent();
4         //在構(gòu)造中設(shè)置Hide或者Show之后立即設(shè)置Hide,均會(huì)導(dǎo)致關(guān)機(jī)阻止失敗
5         Hide();
6     }

在構(gòu)造中Hide或者Show之后立即Hide,均會(huì)導(dǎo)致關(guān)機(jī)阻止失敗。錯(cuò)誤demo,可見 kybs0/ShutdownPreventDemo

我的理解是,ShutdownBlockReasonCreate 函數(shù)需要窗口處于活動(dòng)狀態(tài),窗口Hide之后肯定是不行了。那如何解決呢?

在Loaded之后去設(shè)置窗口隱藏就行了:

 1     public MainWindow()
 2     {
 3         InitializeComponent();
 4         Loaded += MainWindow_Loaded;
 5     }
 6     private void MainWindow_Loaded(object sender, RoutedEventArgs e)
 7     {
 8         Loaded -= MainWindow_Loaded;
 9         //如果啟動(dòng)后需要立即隱藏窗口,請放在Loaded之后
10         Hide();
11     }

設(shè)置Visibility也沒問題 Visibility=Visibility.Collapsed; 驗(yàn)證ok

第二,因?yàn)楦催€是設(shè)置關(guān)機(jī)阻止Resion,那是否可以提前去設(shè)置呢?不要等窗口Hide之后再去設(shè)置或者關(guān)機(jī)時(shí)去設(shè)置...

所以,完全可以在主窗口內(nèi)提前設(shè)置:

 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, "應(yīng)用保存數(shù)據(jù)中,請等待...");
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     }

上面代碼也注釋了,設(shè)置完關(guān)機(jī)原因、再去Hide。關(guān)機(jī)事件觸發(fā)后,是能正常保障阻止機(jī)制的。驗(yàn)證ok

這里也推薦大家使用SystemEvents.SessionEnding方式,可以不受MainWindow窗口的勾子入口限定

3.360安全衛(wèi)士、QQ電腦管家等優(yōu)化軟件,可能會(huì)優(yōu)化此類關(guān)機(jī)阻止機(jī)制

這些安全軟件關(guān)機(jī)時(shí)可能直接強(qiáng)殺,用來提升關(guān)機(jī)/重啟速度。個(gè)人是不建議使用這些安全軟件的,都是流氓。。。

關(guān)機(jī)阻止超時(shí)的情況及建議

關(guān)機(jī)重啟是有時(shí)間限制的,我試了下,在設(shè)置關(guān)機(jī)阻止原因情況下,應(yīng)用最多只能持續(xù)60秒左右

超過60s后系統(tǒng)取消關(guān)機(jī)、回登錄界面,然后當(dāng)前阻止的進(jìn)程會(huì)在執(zhí)行完Hook后自動(dòng)關(guān)閉(其它進(jìn)程不會(huì)關(guān)閉)

如果Hook勾子內(nèi)我們執(zhí)行的業(yè)務(wù)太過耗時(shí),可能不一定能執(zhí)行完。建議只執(zhí)行更少、必須的業(yè)務(wù)

另外,關(guān)機(jī)時(shí)應(yīng)用關(guān)閉是有順序的。如果想提高一點(diǎn)應(yīng)用關(guān)機(jī)時(shí)應(yīng)用能應(yīng)對的時(shí)間,略微提升關(guān)機(jī)前業(yè)務(wù)執(zhí)行的成功率,可以對進(jìn)程添加關(guān)閉優(yōu)先級(jí):

1         public MainWindow()
2         {
3             InitializeComponent();
4 
5             // 在應(yīng)用程序啟動(dòng)時(shí)調(diào)用
6             SetProcessShutdownParameters(0x4FF, 0);
7         }
8         [DllImport("kernel32.dll")]
9         static extern bool SetProcessShutdownParameters(uint dwLevel, uint dwFlags);

0x100表示最低優(yōu)先級(jí),確保你的程序最先被關(guān)閉

0x4FF表示最高優(yōu)先級(jí),確保你的程序最后被關(guān)閉

詳細(xì)的參考文檔: SetProcessShutdownParameters 函數(shù) (processthreadsapi.h) - Win32 apps | Microsoft Learn

 

 以上demo,可從倉庫獲取 ShutdownPreventDemo: 阻止關(guān)機(jī)demo

轉(zhuǎn)自https://www.cnblogs.com/kybs0/p/18822799?


該文章在 2025/4/14 8:32:26 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲(chǔ)管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产女人乱子对白av片男人日本xxxx麻豆天天 | 国产成人精品一区二区三区在线 | 97久久国产露脸精品国产 | 色一情一乱一伦一区二区三区 | 亚洲午夜久久久精品影院 | 亚洲av无码高清不卡在线观看 | 国产另类69xxxx末成年亚洲成人 | 岛国色情A片无码视频免费看 | 精品人妻午夜一区二区三区 | 自慰系列无码专区 | 亚洲av成人无码久久精品超碰 | 日本一区二区三区中文字幕视频 | 成人一区二区免费中文字幕视频 | 麻豆国产高清精品国在线 | 久久精品国产亚洲AV无码偷窥 | 日韩精品无码一区二区三区四区 | 国产精品久久久久久妇女 | 麻豆md0077饥渴少妇 | 国产午夜鲁丝片av无码免费 | 国产三级av | 少妇无码太爽了不卡 | 精品无码国产自产野外拍在线 | 无码不卡中文字幕一区二区三 | 午夜影院100000| 无码中文中字专区免费视频 | 日韩内射美女人妻一 | 亚洲国产精品无码专区成人 | 一本道久久综合亚洲精品 | 亚洲综合精品伊人久久 | 99久久久无码国产aaa精品 | 少妇人妻综合久久中文字幕 | 亚洲精品一区国产 | 一本大道av伊 | 久久人妻少妇嫩草av无码专 | 成人无码精品1区2区3区免费看 | 草莓丝瓜视频下载-丝瓜视18岁 | 日韩精品福利片午夜免费观 | 国产超碰人人做人人爱ⅴA | 妓院一钑片免看黄大片 | 国产成人av在线播放不卡 | 国产亚洲永久精品电影在线观 |