探测控制台应用何时关闭/被终止?

22

我想为在Linux上使用Mono运行的控制台应用程序创建一个安全退出,但我无法找到一种解决方案,以便检测是否发送了信号或用户按下了Ctrl+C。

在Windows上,有内核函数SetConsoleCtrlHandler可以完成此任务,但这在Mono上不起作用。

如何在我的控制台应用程序上获取关闭事件以使其安全退出?

2个回答

13
您需要使用“Mono.UnixSignal”,这里有一个由Jonathan Pryor发布的很好的示例:http://www.jprl.com/Blog/archive/development/mono/2008/Feb-08.html
还有一个更简短的示例在Mono页面上:FAQ / Technical / Operating System Questions / Signal Handling
// Catch SIGINT and SIGUSR1
UnixSignal[] signals = new UnixSignal [] {
    new UnixSignal (Mono.Unix.Native.Signum.SIGINT),
    new UnixSignal (Mono.Unix.Native.Signum.SIGUSR1),
};

Thread signal_thread = new Thread (delegate () {
    while (true) {
        // Wait for a signal to be delivered
        int index = UnixSignal.WaitAny (signals, -1);

        Mono.Unix.Native.Signum signal = signals [index].Signum;

        // Notify the main thread that a signal was received,
        // you can use things like:
        //    Application.Invoke () for Gtk#
        //    Control.Invoke on Windows.Forms
        //    Write to a pipe created with UnixPipes for server apps.
        //    Use an AutoResetEvent

        // For example, this works with Gtk#    
        Application.Invoke (delegate () { ReceivedSignal (signal); });
    }});

而且对于控制台应用程序,调用会是什么样子?你在 while 循环中缺少一个 }。 - Prix
我在项目中找不到Mono.Posix的引用,你能给我一些额外的指导吗?这两个链接都非常不完整。;/ - Prix
1
@Prix:Mono.Posix.dll是由标准的Mono 2.10.2安装程序安装的,您使用的是哪个发行版?此外,对于控制台应用程序,您可以跳过Application.Invoke,直接从信号线程调用清理逻辑。 - skolima
我正在使用VS2010开发应用程序,因此需要在Windows上进行测试,然后再将其放到Linux服务器上。我知道dll文件在Linux服务器上,但如果我获取该dll并引用它,则无法正常工作,因此我不知道如何继续。 - Prix
2
你无法在Windows上测试此代码,因为它依赖于特定于操作系统的功能。你需要做的是实现两种行为(使用SetConsoleCtrlHandler用于Windows和UnixSignal用于Linux),然后根据Environment.OSVersion.Platform在运行时选择其中一种或另一种 - 在UnixMacOSX上运行时使用UnixSignal - skolima

13

以下是提供Unix和Windows实现的示例。请注意,使用Visual Studio时仍可以包含Mono.Posix dll。

我还添加了SIGTERM信号,因为在Unix中,当将应用程序停止/重新启动为服务时,systemd会触发此信号。

暴露退出事件的接口

public interface IExitSignal
{
    event EventHandler Exit;
}

Unix实现

public class UnixExitSignal : IExitSignal
{
    public event EventHandler Exit;

    UnixSignal[] signals = new UnixSignal[]{
        new UnixSignal(Mono.Unix.Native.Signum.SIGTERM), 
        new UnixSignal(Mono.Unix.Native.Signum.SIGINT),
        new UnixSignal(Mono.Unix.Native.Signum.SIGUSR1)
    };

    public UnixExitSignal()
    {
        Task.Factory.StartNew(() => 
        {
            // blocking call to wait for any kill signal
            int index = UnixSignal.WaitAny(signals, -1);

            if (Exit != null)
            {
                Exit(null, EventArgs.Empty);
            }

        });
    }

}

Windows实现

public class WinExitSignal : IExitSignal
{
    public event EventHandler Exit;

    [DllImport("Kernel32")]
    public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

    // A delegate type to be used as the handler routine
    // for SetConsoleCtrlHandler.
    public delegate bool HandlerRoutine(CtrlTypes CtrlType);

    // An enumerated type for the control messages
    // sent to the handler routine.
    public enum CtrlTypes
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT,
        CTRL_CLOSE_EVENT,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT
    }

    /// <summary>
    /// Need this as a member variable to avoid it being garbage collected.
    /// </summary>
    private HandlerRoutine m_hr;

    public WinExitSignal()
    {
        m_hr = new HandlerRoutine(ConsoleCtrlCheck);

        SetConsoleCtrlHandler(m_hr, true);

    }

    /// <summary>
    /// Handle the ctrl types
    /// </summary>
    /// <param name="ctrlType"></param>
    /// <returns></returns>
    private bool ConsoleCtrlCheck(CtrlTypes ctrlType)
    {
        switch (ctrlType)
        {
            case CtrlTypes.CTRL_C_EVENT:
            case CtrlTypes.CTRL_BREAK_EVENT:
            case CtrlTypes.CTRL_CLOSE_EVENT:
            case CtrlTypes.CTRL_LOGOFF_EVENT:
            case CtrlTypes.CTRL_SHUTDOWN_EVENT:
                if (Exit != null)
                {
                    Exit(this, EventArgs.Empty);
                }
                break;
            default:
                break;
        }

        return true;
    }


}

请注意,我在使用 Docker 容器中的 Mono 时遇到了 Task.Factory.StartNew 的问题,但似乎通过专用后台线程得以解决。是否还有其他人遇到过类似情况? - J Trana
通过SetConsoleCtrlHandler设置的处理程序运行时间限制为5秒。 - irvnriir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接