How to handle “End Task” from Task Manager in .NET 5 console app?

故事扮演 提交于 2021-02-16 14:48:07

问题


My .NET 5 console application registers a HandlerRoutine by using SetConsoleCtrlHandler, so it can do some cleanup before exiting. This allows me to react to CTRL+C/CTRL+BREAK, ALT+F4 and the console being closed using the X button. Sadly, the HandlerRoutine doesn't get called when Task Manager tries to terminate the application after clicking End Task in the Process tab, even though the documentation for HandlerRoutine states the following regarding CTRL_CLOSE_EVENT:

A signal that the system sends to all processes attached to a console when the user closes the console (either by clicking Close on the console window's window menu, or by clicking the End Task button command from Task Manager).

Am I missing something or is the documentation wrong in this case? And is there another way to handle End Task? I also tried the AppDomain.CurrentDomain.ProcessExit event, but it behaves in the same way.

Regarding the End Task button in the Details tab of Task Manager: that one definitely can't be handled, as far as I understand, because it terminates the process immediately.

Below you can find a MWE to reproduce the problem. I'm running it on Windows 10 version 20H2.

// requires NuGet package: System.Windows.Extensions
using System.Media;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace GracefulTermination.ConsoleDotNet5
{
    class Program
    {
        private static readonly TaskCompletionSource terminationTcs = new TaskCompletionSource();
        private static readonly HandlerRoutine handlerRoutine = HandleConsoleCtrl;

        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private delegate bool HandlerRoutine(CtrlType dwCtrlType);

        private enum CtrlType : uint
        {
            CTRL_C_EVENT = 0,
            CTRL_BREAK_EVENT = 1,
            CTRL_CLOSE_EVENT = 2,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT = 6
        }

        static async Task Main()
        {
            SetConsoleCtrlHandler(handlerRoutine, add: true);

            await terminationTcs.Task;
        }

        // FIXME not called when using "End Task" in "Process" tab of Task Manager
        private static bool HandleConsoleCtrl(CtrlType dwCtrlType)
        {
            SystemSound sound = dwCtrlType == CtrlType.CTRL_CLOSE_EVENT
                ? SystemSounds.Hand
                : SystemSounds.Asterisk;

            sound.Play();
            terminationTcs.SetResult();

            return false;
        }

        [DllImport("Kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetConsoleCtrlHandler(
            HandlerRoutine handler,
            [MarshalAs(UnmanagedType.Bool)]
            bool add
        );
    }
}

EDIT 1

I did some more experimenting and it turns out this behavior changes depending on which exact process "End Task" is invoked on.

The picture above shows the process structure when running my application in PowerShell, but cmd.exe behaves in the same way. Processes, which do not call the HandlerRoutine upon clicking "End Task" are marked in 🔴 red and the ones that do call it are marked in 🟢 green. Host für Konsolenfenster is conhost.exe, which I did not attempt to terminate. I also should add that ending the processes marked in 🔴 red randomly does trigger the handler for one try at a time, but this is very rare. For me, handling the termination of the parent process (Windows PowerShell (3)) is the biggest concern, because it's the one most likely to be selected by the user. But I would like to be able to handle all cases, if possible.

EDIT 2

The documentation for SetConsoleCtrlHandler also states the following:

The system generates CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals when the user closes the console, logs off, or shuts down the system so that the process has an opportunity to clean up before termination. Console functions, or any C run-time functions that call console functions, may not work reliably during processing of any of the three signals mentioned previously.

Therefore, I replaced Console.Beep() in the example with SystemSound.Play(), so there are no console functions being used during cleanup.

EDIT 3

The marshaling doesn't seem to be the issue, because HandleConsoleCtrl() is being called and even receives CTRL_CLOSE_EVENT when the console is closed using the X button. It just doesn't work when using End Task. Nevertheless, I did improve the marshaling as recommended by Stephen Cleary.


回答1:


Your marshaling doesn't look quite right. This is what I use:

private enum ConsoleControlEvent : uint
{
    CTRL_C_EVENT = 0,
    CTRL_CLOSE_EVENT = 2,
    CTRL_SHUTDOWN_EVENT = 6,
}

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private delegate bool SetConsoleCtrlHandler_HandlerRoutine(ConsoleControlEvent controlType);

[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlHandler_HandlerRoutine handler,
        [MarshalAs(UnmanagedType.Bool)] bool add);


来源:https://stackoverflow.com/questions/65710454/how-to-handle-end-task-from-task-manager-in-net-5-console-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!