当前位置: 首页 > news >正文

如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束

我的电脑上每天会跑一大堆控制台程序,于是管理这些程序的运行就成了一个问题。或者说你可能也在考虑启动一个控制台程序来完成某些特定的任务。

如果我们需要结束掉这个控制台程序怎么做呢?直接杀进程吗?这样很容易出问题。我正在使用的一个控制台程序会写文件,如果直接杀进程可能导致数据没能写入到文件。所以本文介绍如何使用 .NET/C# 代码向控制台程序发送 Ctrl+C 来安全地结束掉程序。


本文内容

    • 用 Ctrl+C 结束控制台程序
    • Ctrl+C 信号
    • 全部源代码
    • 如何使用

用 Ctrl+C 结束控制台程序

如果直接用 Process.Kill 杀掉进程,进程可能来不及保存数据。所以无论是窗口程序还是控制台程序,最好都让控制台程序自己去关闭。

在这里插入图片描述

▲ 使用 Process.Kill 结束程序,程序退出代码是 -1

在这里插入图片描述

▲ 使用 Ctrl+C 结束程序,程序退出代码是 0

Ctrl+C 信号

Windows API 提供了方法可以将当前进程与目标控制台进程关联起来,这样我们便可以向自己发送 Ctrl+C 信号来结束掉关联的另一个控制台进程。

关联和取消关联的方法是下面这两个,AttachConsoleFreeConsole

[DllImport("kernel32.dll")]
private static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll")]
private static extern bool FreeConsole();

不过,当发送 Ctrl+C 信号的时候,不止我们希望关闭的控制台程序退出了,我们自己程序也是会退出的(即便我们自己是一个 GUI 程序)。所以我们必须先组织自己响应 Ctrl+C 信号。

需要用到另外一个 API:

[DllImport("kernel32.dll")]
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add);

enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

private delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

不过,因为我们实际上并不需要真的对 Ctrl+C 进行响应,只是单纯临时禁用以下,所以我们归这个委托传入 null 就好了。

最后,也是最关键的,就是发送 Ctrl+C 信号了:

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

下面,我将完整的代码贴出来。

全部源代码

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Walterlv.Fracture.Utils
{
    /// <summary>
    /// 提供与控制台程序的交互。
    /// </summary>
    public class ConsoleInterop
    {
        /// <summary>
        /// 关闭控制台程序。
        /// </summary>
        /// <param name="process">要关闭的控制台程序的进程实例。</param>
        /// <param name="timeoutInMilliseconds">如果不希望一直等待进程自己退出,则可以在此参数中设置超时。你可以在超时未推出候采取强制杀掉进程的策略。</param>
        /// <returns>如果进程成功退出,则返回 true;否则返回 false。</returns>
        public static bool StopConsoleProgram(Process process, int? timeoutInMilliseconds = null)
        {
            if (process is null)
            {
                throw new ArgumentNullException(nameof(process));
            }

            if (process.HasExited)
            {
                return true;
            }

            // 尝试将我们自己的进程附加到指定进程的控制台(如果有的话)。
            if (AttachConsole((uint)process.Id))
            {
                // 我们自己的进程需要忽略掉 Ctrl+C 信号,否则自己也会退出。
                SetConsoleCtrlHandler(null, true);

                // 将 Ctrl+C 信号发送到前面已关联(附加)的控制台进程中。
                GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

                // 拾前面已经附加的控制台。
                FreeConsole();

                bool hasExited;
                // 由于 Ctrl+C 信号只是通知程序关闭,并不一定真的关闭。所以我们等待一定时间,如果仍未关闭,则超时不处理。
                // 业务可以通过判断返回值来角是否进行后续处理(例如强制杀掉)。
                if (timeoutInMilliseconds == null)
                {
                    // 如果没有超时处理,则一直等待,直到最终进程停止。
                    process.WaitForExit();
                    hasExited = true;
                }
                else
                {
                    // 如果有超时处理,则超时候返回。
                    hasExited = process.WaitForExit(timeoutInMilliseconds.Value);
                }

                // 重新恢复我们自己的进程对 Ctrl+C 信号的响应。
                SetConsoleCtrlHandler(null, false);

                return hasExited;
            }
            else
            {
                return false;
            }
        }

        [DllImport("kernel32.dll")]
        private static extern bool AttachConsole(uint dwProcessId);

        [DllImport("kernel32.dll")]
        private static extern bool FreeConsole();

        [DllImport("kernel32.dll")]
        private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

        enum CtrlTypes : uint
        {
            CTRL_C_EVENT = 0,
            CTRL_BREAK_EVENT,
            CTRL_CLOSE_EVENT,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT
        }

        private delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);
    }
}

如何使用

现在,我们可以通过调用 ConsoleInterop.StopConsoleProgram(process) 来安全地结束掉一个控制台程序。

当然,为了处理一些意外的情况,我把超时也加上了。下面的用法演示超时 2 秒候程序还没有退出,则强杀。

if (!ConsoleInterop.StopConsoleProgram(process, 2000))
{
    try
    {
        process.Kill();
    }
    catch (InvalidOperationException e)
    {
    }
}

在这里插入图片描述


参考资料

  • signals - Can I send a ctrl-C (SIGINT) to an application on Windows? - Stack Overflow
  • Stopping command-line applications programatically with Ctrl-C event from .Net – a working demo - Nemo’s Realms
  • AttachConsole function - Windows Console - Microsoft Docs
  • SetConsoleCtrlHandler function - Windows Console - Microsoft Docs

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • Windows 10 应用创建模糊背景窗口的三种方法
  • 使用 PInvoke.net Visual Studio Extension 辅助编写 Win32 函数签名
  • 程序员与英语:即时聊天中的英语缩写 lol / lmao / idk
  • 使用 IFTTT 做 RSS 的邮件订阅服务
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • 语法高亮不够漂亮?这里有你想要的 Rouge 主题
  • 理解 UWP 视图的概念,让 UWP 应用显示多个窗口(多视图)
  • UWP 扩展/自定义标题栏的方法,一些概念和一些注意事项
  • 图片点击放大,你的网页也能做到!
  • UWP 应用中 CoreApplication / Application, CoreWindow / Window 之间的区别
  • 使用 C# 代码创建快捷方式文件
  • 发布了一款库(或工具包),如何持续地编写更新日志(ChangeLog)?
  • Windows 无法删除文件夹 —— 访问被拒绝 / 因为目录不是空的
  • 如何精准地用打印机在贺卡或邀请函上打字
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • 【刷算法】求1+2+3+...+n
  • Angular 响应式表单 基础例子
  • conda常用的命令
  • Django 博客开发教程 8 - 博客文章详情页
  • dva中组件的懒加载
  • ECMAScript6(0):ES6简明参考手册
  • ES10 特性的完整指南
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • Java程序员幽默爆笑锦集
  • leetcode388. Longest Absolute File Path
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • webpack4 一点通
  • 程序员最讨厌的9句话,你可有补充?
  • 对象管理器(defineProperty)学习笔记
  • 分享几个不错的工具
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 收藏好这篇,别再只说“数据劫持”了
  • 我与Jetbrains的这些年
  • Spring第一个helloWorld
  • 交换综合实验一
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • (done) 两个矩阵 “相似” 是什么意思?
  • (solr系列:一)使用tomcat部署solr服务
  • (SpringBoot)第二章:Spring创建和使用
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • ******之网络***——物理***
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET MVC第三章、三种传值方式
  • .net反编译的九款神器
  • ::
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • [2009][note]构成理想导体超材料的有源THz欺骗表面等离子激元开关——
  • [ASP]青辰网络考试管理系统NES X3.5
  • [AutoSar]BSW_Com02 PDU详解