.NET 阻止Windows关机以及阻止失败的一些原因
<p>本文主要介绍Windows在关闭时,如何正确、可靠的阻止系统关机以及关机前执行相应业务</p><p>Windows关机,默认会给应用几s的关闭时间,但有一些场景需要在关机/重启前执行更长时间的业务逻辑,确保下次开机时数据的一致性以及可靠性。我司目前业务也用到关机阻止,但这块之前并未梳理清楚,依赖BUG编程,导致后续维护项目时关机这块又会出现新问题。</p>
<p>统一整理,以下是实现这一需求的几种方法,</p>
<h3>1. Windows消息Hook勾子</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow()
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)"> InitializeComponent();
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> Loaded +=<span style="color: rgba(0, 0, 0, 1)"> OnLoaded;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> OnLoaded(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, RoutedEventArgs e)
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> Loaded -=<span style="color: rgba(0, 0, 0, 1)"> OnLoaded;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 255, 1)">var</span> source = PresentationSource.FromVisual(<span style="color: rgba(0, 0, 255, 1)">this</span>) <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> HwndSource;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> source?<span style="color: rgba(0, 0, 0, 1)">.AddHook(WndProc);
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> WM_QUERYENDSESSION = <span style="color: rgba(128, 0, 128, 1)">0x11</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">int</span> WM_ENDSESSION = <span style="color: rgba(128, 0, 128, 1)">0x16</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 255, 1)">private</span> IntPtr WndProc(IntPtr hwnd, <span style="color: rgba(0, 0, 255, 1)">int</span> msg, IntPtr wParam, IntPtr lParam, <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> handled)
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (msg ==<span style="color: rgba(0, 0, 0, 1)"> WM_QUERYENDSESSION)
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">var</span> handle = <span style="color: rgba(0, 0, 255, 1)">new</span> WindowInteropHelper(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).Handle;
</span><span style="color: rgba(0, 128, 128, 1)">20</span> ShutdownBlockReasonCreate(handle, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">应用保存数据中,请等待...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可以在这里执行你的业务逻辑</span>
<span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> executeSuccess =<span style="color: rgba(0, 0, 0, 1)"> ExecuteShutdownWork();
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回0表示阻止关机,1表示允许关机</span>
<span style="color: rgba(0, 128, 128, 1)">24</span> handled = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 255, 1)">return</span> executeSuccess ? (IntPtr)<span style="color: rgba(128, 0, 128, 1)">1</span> : (IntPtr)<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 255, 1)">return</span> (IntPtr)<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ExecuteShutdownWork()
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">32</span> Thread.Sleep(TimeSpan.FromSeconds(<span style="color: rgba(128, 0, 128, 1)">20</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">测试,默认返回操作失败</span>
<span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">36</span>
<span style="color: rgba(0, 128, 128, 1)">37</span>
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> ShutdownBlockReasonCreate(IntPtr hWnd, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> reason);
</span><span style="color: rgba(0, 128, 128, 1)">39</span>
</span><span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> ShutdownBlockReasonDestroy(IntPtr hWnd);</pre>
</div>
<p>通过Hook循环windows窗口消息,WndProc接收到WM_QUERYENDSESSION时表示有关机调用,详细的可以查看官网文档:(WinUser.h) WM_QUERYENDSESSION消息 - Win32 apps | Microsoft Learn</p>
<p>WndProc返回1表示业务正常,0表示取消、阻止关机。这里我们默认操作失败,阻止关机</p>
<p>拿到每个应用的关机确认结果,再广播WM_ENDSESSION、执行真正的关闭</p>
<p>拿到窗口句柄,可以通过ShutdownBlockReasonCreate设置阻止关机原因,ShutdownBlockReasonDestroy清理关机阻止原因,详见:ShutdownBlockReasonCreate 函数 (winuser.h) - Win32 apps | Microsoft Learn</p>
<p>阻止进行中的效果:</p>
<p><img src="https://img2024.cnblogs.com/blog/685541/202504/685541-20250413020900637-234149948.png" alt="" width="607" height="496" loading="lazy"></p>
<p>上面demo运行20s之后,系统会退出关机状态、返回登录界面</p>
<h3>2.Win32系统事件SystemEvents</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> App : Application
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> App()
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> SystemEvents.SessionEnding +=<span style="color: rgba(0, 0, 0, 1)"> SystemEvents_SessionEnding;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> Application.Current.Exit +=<span style="color: rgba(0, 0, 0, 1)"> Current_Exit;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Current_Exit(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, ExitEventArgs e)
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> SystemEvents.SessionEnding -=<span style="color: rgba(0, 0, 0, 1)"> SystemEvents_SessionEnding;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> SystemEvents_SessionEnding(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, SessionEndingEventArgs e)
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (e.Reason ==<span style="color: rgba(0, 0, 0, 1)"> SessionEndReasons.SystemShutdown)
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 255, 1)">var</span> handle = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WindowInteropHelper(Application.Current.MainWindow).Handle;
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)"> ShutdownBlockReasonDestroy(handle);
</span><span style="color: rgba(0, 128, 128, 1)">18</span> ShutdownBlockReasonCreate(handle, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">应用保存数据中,请等待...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 255, 1)">var</span> executeSuccess =<span style="color: rgba(0, 0, 0, 1)"> ExecuteShutdownWork();
</span><span style="color: rgba(0, 128, 128, 1)">21</span> e.Cancel = !<span style="color: rgba(0, 0, 0, 1)">executeSuccess;
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ExecuteShutdownWork()
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Test</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> Thread.Sleep(TimeSpan.FromSeconds(<span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 255, 1)">try</span>
<span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> XXX</span>
<span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">37</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">39</span>
<span style="color: rgba(0, 128, 128, 1)">40</span>
</span><span style="color: rgba(0, 128, 128, 1)">41</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> ShutdownBlockReasonCreate(IntPtr hWnd, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> reason);
</span><span style="color: rgba(0, 128, 128, 1)">42</span>
</span><span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ShutdownBlockReasonDestroy(IntPtr hWnd);
</span><span style="color: rgba(0, 128, 128, 1)">44</span> }</pre>
</div>
<p>也可以监听SessionEndReasons.SystemShutdown关机事件。实际上也是基于消息机制,但封装了细节、提供更高级抽象</p>
<p>这里e.Cancel,false表示不取消用户请求、不关机,true表示取消用户请求、<span class="token comment">阻止关机</span></p>
<p>因为需要设置关机阻止原因,SystemEvents.SessionEnding也是要依赖窗口的。当然,因为依赖窗口会导致勾子失败,下面我们会聊</p>
<p>SessionEndReasons还有另一选项Logoff注销,也是可以阻止的。当然因为系统并未关机,注销时加的逻辑与关机大家要根据具体业务区分下</p>
<h3><span class="token comment">阻止关机失败的一些原因</span></h3>
<p><span class="token comment">以上俩种方式,均可以实现阻止系统关机以及关机前执行相应业务。但Hook勾子也可能失效,不能正常执行完你的业务逻辑</span></p>
<p><strong><span class="token comment">1. 关机勾子只支持UI线程,不支持异步调用</span></strong></p>
<p><span class="token comment">第一种,SessionEnding事件被修改为了async void</span></p>
<p><span class="token comment">第二种,业务内部调用了异步方法,通过.Result、.Wait()期望等待完成。但其实内部并没有完全添加.ConfigureAwait,这也会导致关机阻止失败。</span></p>
<p><span class="token comment">我司业务就遇到了第二个问题,在消息循环WndProc之后,添加了上报后台日志。为了满足勾子只支持同步调用,日志模块就使用了.Result转为同步方法:<br></span></p>
<p><img src="https://img2024.cnblogs.com/blog/685541/202504/685541-20250414014523366-1879452952.png" alt="" loading="lazy"></p>
<p>对于这类限定UI线程同步执行场景,我的解决办法是,减少逻辑、去除发送后台日志。</p>
<p><span class="token comment">另外,如果下面业务真的需要使用async,需要业务上下游所有调用链条均添加.ConfigureAwait,不切换上下文。否则系统不会等待、往下直接关机了</span></p>
<p><strong><span class="token comment">2. 窗口Hide,导致勾子失效</span></strong></p>
<p>一些窗口启动后,需要立即Hide窗口:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow()
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 0, 1)"> InitializeComponent();
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">在构造中设置Hide或者Show之后立即设置Hide,均会导致关机阻止失败</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 0, 1)"> Hide();
</span><span style="color: rgba(0, 128, 128, 1)">6</span> }</pre>
</div>
<p>在构造中Hide或者Show之后立即Hide,均会导致关机阻止失败。错误demo,可见 kybs0/ShutdownPreventDemo</p>
<p id="shutdownblockreasoncreate-function-winuserh">我的理解是,ShutdownBlockReasonCreate 函数<span class="token comment">需要窗口处于活动状态,初始化窗口之后立即Hide会让窗口处于非活动状态。那如何解决呢?</span></p>
<p><span class="token comment">在Loaded之后去设置窗口隐藏就行了:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow()
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)"> InitializeComponent();
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> Loaded +=<span style="color: rgba(0, 0, 0, 1)"> MainWindow_Loaded;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> MainWindow_Loaded(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, RoutedEventArgs e)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> Loaded -=<span style="color: rgba(0, 0, 0, 1)"> MainWindow_Loaded;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果启动后需要立即隐藏窗口,请放在Loaded之后</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)"> Hide();
</span><span style="color: rgba(0, 128, 128, 1)">11</span> }</pre>
</div>
<p>设置Visibility也没问题 Visibility=Visibility.Collapsed; <span style="color: rgba(0, 128, 128, 1)">验证ok</span></p>
<p><span class="token comment">第二,因为根源还是设置关机阻止Resion,那是否可以提前去设置呢?不要等窗口Hide之后再去设置或者关机时去设置...</span></p>
<p>所以,完全可以在主窗口内提前设置:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow : Window
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow()
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)"> InitializeComponent();
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> Loaded +=<span style="color: rgba(0, 0, 0, 1)"> MainWindow_Loaded;
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> MainWindow_Loaded(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, RoutedEventArgs e)
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> Loaded -=<span style="color: rgba(0, 0, 0, 1)"> MainWindow_Loaded;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 255, 1)">var</span> currentMainWindow =<span style="color: rgba(0, 0, 0, 1)"> Application.Current.MainWindow;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 255, 1)">var</span> handle = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WindowInteropHelper(currentMainWindow).Handle;
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> ShutdownBlockReasonDestroy(handle);
</span><span style="color: rgba(0, 128, 128, 1)">14</span> ShutdownBlockReasonCreate(handle, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">应用保存数据中,请等待...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">窗口Hide,并不影响上面的ShutdownBlockReasonDestroy</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)"> Hide();
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> ShutdownBlockReasonCreate(IntPtr hWnd, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> reason);
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> ShutdownBlockReasonDestroy(IntPtr hWnd);
</span><span style="color: rgba(0, 128, 128, 1)">23</span> }</pre>
</div>
<p>上面代码也注释了,设置完关机原因、再去Hide。关机事件触发后,是能正常保障阻止机制的。<span style="color: rgba(0, 128, 128, 1)">验证ok</span></p>
<p><span style="color: rgba(0, 0, 0, 1)">这里也推荐大家使用SystemEvents.SessionEnding方式,可以不受MainWindow窗口的勾子入口限定</span></p>
<p><strong><span class="token comment">3.360安全卫士、QQ电脑管家等优化软件,可能会优化此类关机阻止机制</span></strong></p>
<p><span class="token comment">这些安全软件关机时可能直接强杀,用来提升关机/重启速度。个人是不建议使用这些安全软件的,都是流氓。。。</span></p>
<h3>关机阻止超时的情况及建议</h3>
<p>关机重启是有时间限制的,我试了下,在设置关机阻止原因情况下,<strong>应用最多只能持续60秒左右</strong>。</p>
<p>超过60s后系统取消关机、回登录界面,然后<strong>当前阻止的进程会在执行完Hook后自动关闭</strong>(其它进程不会关闭)</p>
<p>如果Hook勾子内我们执行的业务太过耗时,可能不一定能执行完。建议只执行更少、必须的业务</p>
<p>另外,关机时应用关闭是有顺序的。如果想提高一点应用关机时应用能应对的时间,略微提升关机前业务执行的成功率,可以对进程添加关闭优先级:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainWindow()
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 0, 1)"> InitializeComponent();
</span><span style="color: rgba(0, 128, 128, 1)">4</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在应用程序启动时调用</span>
<span style="color: rgba(0, 128, 128, 1)">6</span> SetProcessShutdownParameters(<span style="color: rgba(128, 0, 128, 1)">0x4FF</span>, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">7</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">8</span>
</span><span style="color: rgba(0, 128, 128, 1)">9</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">extern</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> SetProcessShutdownParameters(<span style="color: rgba(0, 0, 255, 1)">uint</span> dwLevel, <span style="color: rgba(0, 0, 255, 1)">uint</span> dwFlags);</pre>
</div>
<p>0x100表示最低优先级,确保你的程序最先被关闭</p>
<p>0x4FF表示最高优先级,确保你的程序最后被关闭</p>
<p>详细的参考文档: SetProcessShutdownParameters 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn</p>
<p> </p>
<p> 以上demo,可从仓库获取 ShutdownPreventDemo: 阻止关机demo</p>
</div>
<div id="MySignature" role="contentinfo">
<div>作者:唐宋元明清2188</div>
<div>出处:http://www.cnblogs.com/kybs0/</div>
<div>让学习成为习惯,假设明天就有重大机遇等着你,你准备好了么</div>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。 </div><br><br>
来源:https://www.cnblogs.com/kybs0/p/18822799
頁:
[1]