问题
I'm using the following c # code to temporarily block the shutdown of a WinForm application without success, what I observe is that the System doesn't shutdown at all, probably because the work I have to do when receiving the shutdown notification is being made on the UI thread. Windows does not terminate the application if the application is unresponsive after 30 secs as documented. See the attached image.enter image description here
public Form1()
{
InitializeComponent();
// Define the priority of the application (0x3FF = The higher priority)
SetProcessShutdownParameters(0x3FF, SHUTDOWN_NORETRY);
}
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);
[DllImport("kernel32.dll")]
static extern bool SetProcessShutdownParameters(uint dwLevel, uint dwFlags);
private static int WM_QUERYENDSESSION = 0x11;
private static int WM_ENDSESSION = 0x16;
public const uint SHUTDOWN_NORETRY = 0x00000001;
private ManualResetEvent rEvent = new ManualResetEvent(false);
private bool blocked = false;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_QUERYENDSESSION)
{
if (!blocked)
{
blocked = true;
ShutdownBlockReasonCreate(this.Handle, "Closing in progress");
this.BeginInvoke((Action)(() =>
{
// My clean-up work on UI thread
Thread.Sleep(600000);
// Allow Windows to shutdown
ShutdownBlockReasonDestroy(this.Handle);
}));
m.Result = IntPtr.Zero;
}
else
{
m.Result = (IntPtr)1;
}
}
if (m.Msg == WM_ENDSESSION)
{
if (blocked)
{
ShutdownBlockReasonDestroy(this.Handle);
m.Result = (IntPtr)1;
}
}
// If this is WM_QUERYENDSESSION, the closing event should be
// raised in the base WndProc.
base.WndProc(ref m);
} //WndProc
回答1:
Note: don't test this functionality from the Visual Studio IDE:
Build the executable and run the Application from it.
Toggle private bool AllowEndSession
true/false to disable/enable the System restart block.
It simulates a busy application that still needs to complete its work when the WM_QUERYENDSESSION message is received. Of course your App needs to be in a condition to respond to this message: the UI thread must be responsive (i.e., the App is doing work on a thread other than the UI thread).
Also, you should evaluate lParam
, since it could be ENDSESSION_CRITICAL
(the System itself may be forced to shut-down - power shortage and UPC running on fumes, as a possible edge case. A critical System Service failure as a more generic cause).
If the Application is not busy, but it requires to perform clean-up operations or other tasks that may take more time, it should return FALSE
(IntPtr.Zero
) when WM_QUERYENDSESSION
is received and initiate the procedure that is the reason of the delay request when it receives WM_ENDSESSION
When an application returns
TRUE
for this message, it receives theWM_ENDSESSION
message, regardless of how the other applications respond to theWM_QUERYENDSESSION
message. Each application should returnTRUE
orFALSE
immediately upon receiving this message, and defer any cleanup operations until it receives theWM_ENDSESSION
message.
As a note, the request of a block should be used only when User data can be compromised for specific reasons or some hardware is completing an operation (as a CD/DVD write). Other procedures/tasks performed by the application must be completed all in due time.
When AllowEndSession = false;
and the Application receives a WM_QUERYENDSESSION
message, a System.Windows.Forms.Timer
is started, with a time-out of 10 seconds (simulating a busy but responsive application the will take that amount of time to terminate a critical job).
The System will present to the User the classic restart block screen, informing that an App has requested to delay the restart process, showing the block reason string the App provided (The Cleaning Up/Doing stuff... Wait a sec
passed to ShutdownBlockReasonCreate
, here).
When the timer elapses (ticks), the job is terminated and the App will close its Main Form, call Application.Exit
or whatever and also call ShutdownBlockReasonDestroy()
.
At this point, the Shut-Down/Restart procedure will resume, the System screen will update its status, close the remaining application, if any, still running and proceed to shut down.
private bool AllowEndSession = false;
private bool ShutbownBlockReasonCreated = false;
private System.Windows.Forms.Timer shutDownTimer = null;
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_QUERYENDSESSION:
if (!AllowEndSession) {
bool result = ShutdownBlockReasonCreate(this.Handle, "Cleaning Up/Doing stuff... Wait a sec");
shutDownTimer = new System.Windows.Forms.Timer();
shutDownTimer.Tick += (s, evt) => {
ShutbownBlockReasonCreated = false;
ShutdownBlockReasonDestroy(this.Handle);
shutDownTimer.Enabled = false;
shutDownTimer.Dispose();
this.Close();
};
shutDownTimer.Interval = 10000;
shutDownTimer.Enabled = true;
ShutbownBlockReasonCreated = true;
m.Result = IntPtr.Zero;
}
else {
m.Result = (IntPtr)1;
}
break;
case WM_ENDSESSION:
if (ShutbownBlockReasonCreated) {
ShutdownBlockReasonDestroy(this.Handle);
}
m.Result = (IntPtr)1;
break;
default:
base.WndProc(ref m);
break;
}
}
来源:https://stackoverflow.com/questions/58630032/preventing-winform-application-shutdown-not-working