Preventing WinForm application shutdown not working

0

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
winforms
application-shutdown
asked on Stack Overflow Oct 30, 2019 by Francesco • edited Nov 4, 2019 by Francesco

1 Answer

0

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 the WM_ENDSESSION message, regardless of how the other applications respond to the WM_QUERYENDSESSION message. Each application should return TRUE or FALSE immediately upon receiving this message, and defer any cleanup operations until it receives the WM_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;
    }
}
answered on Stack Overflow Nov 4, 2019 by Jimi • edited Nov 4, 2019 by Jimi

User contributions licensed under CC BY-SA 3.0