Windows 上的 .NET Core 应用程序可以捕获 SIGTERM 事件吗?

Can a .NET Core application on Windows trap a SIGTERM event?

我很好奇在 Windows 上使用 .NET Core 运行ning 编写的控制台应用程序是否可以拦截 SIGKILL 事件并且基本上知道它正在终止。这是我正在尝试的代码:

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"Hello from PID {Process.GetCurrentProcess().Id}, press CTRL+C to exit.");

        AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += context =>
        {
            Console.WriteLine("SIGTERM received, exiting program...");
        };

        Console.CancelKeyPress += (s, e) =>
        {
            Console.WriteLine("SIGINT received, exiting program...");
            Environment.Exit(0);
        };

        try
        {
            await Task.Delay(Timeout.Infinite);
        }
        finally
        {
            Console.WriteLine("Finally executed..");
        }

    }

当我从命令行 运行 程序时,我可以使用 CTRL+C 组合键终止它。这将触发 CancelKeyPress 以及 Unloading。如果我以其他方式终止程序(使用 Windows 任务管理器 "End Process" 函数,或 Stop-Process PowerShell 命令),该过程将直接结束,而不会向控制台写入任何输出。

这是更大目标的一部分,即捕获 Docker 容器关闭。在 Windows 上,docker stop 命令将使用 SIGTERM 终止主进程。此行为可使用 --stop-signalSTOPSIGNAL 功能在 Linux Docker 上配置,但这些功能尚未在 Windows.

上实现

On Windows, the docker stop command will kill the main process using SIGTERM.

As of version 1709 (for both host and base image), Windows containers using the base image microsoft/windowsservercore or microsoft/nanoserver will send CTRL_CLOSE_EVENT to the main process only.

As of version 1803, Windows containers using those base images will send CTRL_SHUTDOWN_EVENT to all processes running in the container.

因此,根据您的版本,Docker 可能会发送 CTRL_CLOSE_EVENTCTRL_SHUTDOWN_EVENT

这些不同于 Ctrl-C 信号,后者是 CTRL_C_EVENT.NET's Console.CancelKeyPress only handles CTRL_C_EVENT (or the closely-related CTRL_BREAK_EVENT),因此不会为 CTRL_CLOSE_EVENTCTRL_SHUTDOWN_EVENT 调用。

If I terminate the program other ways (Using the Windows Task Manager "End Process" function, or the Stop-Process PowerShell command), the process simply ends without any output written to the console.

不可能处理所有终止信号。我相信这些天任务管理器试图做一个 "gentle" 关闭,在这种情况下可能会发送 CTRL_CLOSE_EVENT,但也完全有可能只 TerminateProcess 一个进程,它会立即杀死它而不会任何信号或任何东西。

但识别 Docker 启动的关闭并对其做出响应是可能的,因为它首先发送一个良好的信号并且仅在超时后终止。

The .NET Runtime will recognize CTRL_CLOSE_EVENT as a process exit request, but since newer versions of Docker for Windows have switched to CTRL_SHUTDOWN_EVENT, I believe that .NET will simply not do a graceful shutdown for Windows containers anymore.

我知道的唯一解决方法是安装您自己的控制台控件事件处理程序。我推荐handling CTRL_C_EVENT, CTRL_CLOSE_EVENT, and CTRL_SHUTDOWN_EVENT all as just a generic "shutdown" signal.

我想 post .NET Core 控制台应用程序的完整工作代码,当 运行 在 Docker 容器中(使用 docker run -d)时,可以响应 docker stop 命令。请永远使用它!

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace DockerTest
{
    class Program
    {
        private enum ConsoleControlEvent : uint { CTRL_C_EVENT = 0, CTRL_CLOSE_EVENT = 2, CTRL_SHUTDOWN_EVENT = 6 }

        private static void ConsoleCtrlHandler(ConsoleControlEvent controlType)
        {
            if (controlType == ConsoleControlEvent.CTRL_C_EVENT || controlType == ConsoleControlEvent.CTRL_CLOSE_EVENT ||
                controlType == ConsoleControlEvent.CTRL_SHUTDOWN_EVENT)
            {
                Console.WriteLine("Docker container is shutting down..");
                Environment.Exit(0);
            }
        }

        [UnmanagedFunctionPointer(CallingConvention.Winapi)]
        private delegate void SetConsoleCtrlHandler_HandlerRoutine(ConsoleControlEvent controlType);

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

        static void Main()
        {
            Console.CancelKeyPress += (_, args) => { };
            if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, add: true))
                throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

            Console.WriteLine("Waiting to be stopped.");
            Thread.Sleep(-1);
        }
    }
}

证据如下:

主要感谢@Stephen Cleary,所以请为他的回答点赞。我不是英雄