前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束

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

作者头像
walterlv
发布2023-10-22 10:47:33
9610
发布2023-10-22 10:47:33
举报

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

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


用 Ctrl+C 结束控制台程序

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

Process.Kill 结束控制台程序
Process.Kill 结束控制台程序

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

Ctrl+C 结束控制台程序
Ctrl+C 结束控制台程序

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

Ctrl+C 信号

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

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

1 2 3 4 5

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

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

需要用到另外一个 API:

1 2 3 4 5 6 7 8 9 10 11 12 13

[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 信号了:

1 2 3

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

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

全部源代码

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

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 秒候程序还没有退出,则强杀。

1 2 3 4 5 6 7 8 9 10

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

Ctrl+C 结束控制台程序
Ctrl+C 结束控制台程序

参考资料

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/shutdown-a-console-program-safely-using-ctrl-c.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用 Ctrl+C 结束控制台程序
  • Ctrl+C 信号
  • 全部源代码
  • 如何使用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档