Search code examples
c#winapipower-managementwin32-process

Detect PowerModeChange and wait for execution


I want to run some save routines when the computer suspends. Therefore I use the OnPowerChange-Event to detect when it suspends and resumes. Unfortunately me save routine needs 3-5 seconds to execute.

When I receive the suspend event the computer shuts down within 1-2 seconds and my routine isn't executed completely.

How can I prevent the suspend until my routine is finished?

SystemEvents.PowerModeChanged += OnPowerChange;


private void OnPowerChange(object s, PowerModeChangedEventArgs e)
{

    switch (e.Mode)
    {
        case PowerModes.Resume:
            switchEdifier(true);
            break;
        case PowerModes.Suspend:
            switchEdifier(false);
            break;
    }
}

Solution

  • There are some unmanaged APIs that can help with this, specifically ShutdownBlockReasonCreate and ShutdownBlockReasonDestroy.

    Its important to note that these two functions must be paired, when you call one, you have to make sure you call the other one (for example, in the event of an exception), otherwise the shutdown may be blocked indefinitely.

    This will cause a dialog to show up telling the user what programs are blocking shutdown, and a reason for it. Its important that you get your work done quickly and get out, because the user has the option of hitting the "Force Shutdown" button, which they often use.

    Here is an example using it:

    [DllImport("user32.dll", SetLastError=true)]
    static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
    
    [DllImport("user32.dll", SetLastError=true)]
    static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
    
    //The following needs to go in a Form class, as it requires a valid window handle
    public void BlockShutdownAndSave()
    {
        //If calling this from an event, you may need to invoke on the main form
        //because calling this from a thread that is not the owner of the Handle
        //will cause an "Access Denied" error.
    
        try
        {
            ShutdownBlockReasonCreate(this.Handle, "You need to be patient.");
            //Do your saving here.
        }
        finally
        {
            ShutdownBlockReasonDestroy(this.Handle);
        }
    }
    

    Short strings for the reason are encouraged as the user typically won't read long messages. Something that grabs attention, like "Saving data" or "Flushing changes to disk". Just be mindful of the "do it anyway I'm an impatient user" button.