简短可靠的答案:
在任何最新的 Windows 版本上,您可以尝试取消关机,但 Windows 可能会决定忽略您;可悲的是,这是设计使然。如果您必须在进程终止之前完成操作,那么执行此操作的正确位置是在 SessionEnded 处理程序中。如果您有必须在流程终止之前完成的任务,则在完成所有工作之前,您不得从 SessionEnded 处理程序返回(因此您的查询等已完成。)
因此,不是(或者,如果您愿意,也可以)处理 SessionEnding,处理 SessionEnded 并在那里工作:
static void SystemEvents_SessionEnded(object sender, SessionEndedEventArgs e)
{
WaitForQueriesToFinishOrSaveState(); // new code
}
您如何实现等待将取决于您的应用程序;如果您需要重新运行查询,您可以在其中执行它们,或者您可能需要 Thread.Join() 或以其他方式等待后台任务完成;但它必须是阻塞等待(所以在完成之前你不需要从函数返回)。
由于您不能完全停止关机,因此在这种情况下尝试取消可能没有什么意义,所以我建议不要在 SessionEnding 中设置 e.Cancel 。在较旧的 Windows 版本上,这更有意义,不幸的是,现在意义不大。
API 文档 还建议不要在 SessionEnding 中做任何重要的工作(包括消息框),而是设置您需要立即返回的任何标志,然后在 SessionEnded 中进行工作。(未经证实:我怀疑如果你没有足够快地返回,这可能会加速用户出现“程序行为不端,你想杀死它们”屏幕,因为 Windows 认为你没有在玩再好不过了。)
幕后花絮:
设置 e.Cancel 向 Windows 表明您不希望会话结束;但是用户仍然可以查看;并且 Windows 可能会出于任何相关的原因决定忽略您的请求。这就是饼干碎的方式。您可能会发现在某些情况下可以使用的 hack,但是没有任何 API 或方法是 Microsoft 批准的,因此现在和将来都可能始终如一地工作。
在幕后,Windows 正在向您的进程的窗口发送一条 WM_QUERYENDSESSION 消息,.NET 会为您接收并转换为 SessionEnding 事件)并将您的取消(或缺少)传递回 Windows;如果不取消则返回 TRUE,如果取消则返回 FALSE。
在此之后,Windows 会查看所有进程的请求,并且根据关闭的原因和其他因素,尽管有这些请求,仍然可能决定继续执行。如果进程不合作,它还可以提醒用户并让他们选择终止进程。
无论您是否处理 WM_QUERYENDSESSION (SessionEnding),您总是有最后一次清理的机会:您会收到一条 WM_ENDSESSION 消息(翻译为 SessionEnded)。棘手的部分是您必须在所有 SessionEnded 处理程序返回之前完成所有重要任务!
一旦 Windows 从它的 WM_ENDSESSION (SessionEnded) 调用中收到回复,就您的应用程序的生命周期而言,所有的赌注都将被取消,并且 Windows 可以随时终止您的进程。
Raymond Chen 最近非常专业地报道了这一点。
http://blogs.msdn.com/b/oldnewthing/archive/2013/06/27/10429232.aspx
作为脚注, SystemEvents.SessionEnded 是一种方便;如果您有一个顶级应用程序窗口,您可以完全绕过它并通过以下方式实现相同的目的:
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x16) // WM_ENDSESSION
{
WaitForQueriesToFinishOrSaveState();
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}