如何在 C# 控制台应用程序中捕获 ctrl-c (SIGINT)
Posted
技术标签:
【中文标题】如何在 C# 控制台应用程序中捕获 ctrl-c (SIGINT)【英文标题】:How do I trap ctrl-c (SIGINT) in a C# console app 【发布时间】:2010-09-15 17:24:33 【问题描述】:我希望能够在 C# 控制台应用程序中捕获 CTRL+C 以便我可以在退出之前进行一些清理。这样做的最佳方法是什么?
【问题讨论】:
【参考方案1】:Console.CancelKeyPress 事件用于此目的。它是这样使用的:
public static void Main(string[] args)
Console.CancelKeyPress += delegate
// call methods to clean up
;
while (true)
当用户按下 Ctrl + C 时,委托中的代码运行并且程序退出。这允许您通过调用必要的方法来执行清理。注意委托执行后没有代码。
在其他情况下这不会削减它。例如,如果程序当前正在执行无法立即停止的重要计算。在这种情况下,正确的策略可能是在计算完成后告诉程序退出。以下代码给出了如何实现的示例:
class MainClass
private static bool keepRunning = true;
public static void Main(string[] args)
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
e.Cancel = true;
MainClass.keepRunning = false;
;
while (MainClass.keepRunning)
// Do your work in here, in small chunks.
// If you literally just want to wait until ctrl-c,
// not doing anything, see the answer using set-reset events.
Console.WriteLine("exited gracefully");
这段代码和第一个例子的不同之处在于e.Cancel
被设置为true,这意味着在delegate之后继续执行。如果运行,程序会等待用户按 Ctrl + C。当这种情况发生时,keepRunning
变量会更改值,这会导致 while 循环退出。这是一种让程序优雅退出的方法。
【讨论】:
keepRunning
可能需要标记为volatile
。否则主线程可能会将其缓存在 CPU 寄存器中,并且在执行委托时不会注意到值的变化。
这行得通,但它不会用 X 来限制窗口的关闭。请参阅下面的完整解决方案。也适用于 kill。
应该改为使用ManualResetEvent
,而不是使用bool
。
对于在 Git-Bash、MSYS2 或 CygWin 中运行的任何其他人的一个小警告:您必须通过 winpty 运行 dotnet 才能使其工作(所以,winpty dotnet run
)。否则,代理将永远不会运行。
注意 - CancelKeyPress 的事件是在线程池线程上处理的,这不是很明显:docs.microsoft.com/en-us/dotnet/api/…【参考方案2】:
参见 MSDN:
Console.CancelKeyPress Event
带有代码示例的文章:
Ctrl-C and the .NET console application
【讨论】:
其实那篇文章推荐的是P/Invoke,而CancelKeyPress
只是在cmets中简单提及。一篇好文章是codeneverwritten.com/2006/10/…
这行得通,但它不会用 X 来限制窗口的关闭。请参阅下面的完整解决方案。也适用于 kill
我发现如果控制台关闭,Console.CancelKeyPress 将停止工作。使用 systemd 在 mono/linux 下运行应用程序,或者如果应用程序以“mono myapp.exe ***.com/questions/6546509/…
这是上述文章的永久链接:codeneverwritten.blogspot.com/2006/10/… 在搜索 Ctrl-C 处理时遇到我自己关于 SO 的文章很有趣。 :)【参考方案3】:
我想添加到Jonas' answer。在bool
上旋转将导致 100% 的 CPU 利用率,并在等待 CTRL+C 时浪费大量精力。
更好的解决方案是使用ManualResetEvent
实际“等待” CTRL+C:
static void Main(string[] args)
var exitEvent = new ManualResetEvent(false);
Console.CancelKeyPress += (sender, eventArgs) =>
eventArgs.Cancel = true;
exitEvent.Set();
;
var server = new MyServer(); // example
server.Run();
exitEvent.WaitOne();
server.Stop();
【讨论】:
我认为关键是您将在 while 循环内完成所有工作,并且按 Ctrl+C 不会在 while 迭代中间中断;它将在爆发之前完成该迭代。 @pkr298 - 可惜人们没有投票赞成您的评论,因为它完全正确。我将编辑乔纳斯的答案,以澄清人们不会像乔纳森那样思考(这本身并不坏,但不是乔纳斯的意思) 请注意,虽然在 VS 调试控制台窗口中按下 Ctrl-C 时会调用CancelKeyPress
事件处理程序,但设置 Cancel = true
无效。【参考方案4】:
这是一个完整的工作示例。粘贴到空的 C# 控制台项目中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace TestTrapCtrlC
public class Program
static bool exitSystem = false;
#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
private delegate bool EventHandler(CtrlType sig);
static EventHandler _handler;
enum CtrlType
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6
private static bool Handler(CtrlType sig)
Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");
//do your cleanup here
Thread.Sleep(5000); //simulate some cleanup delay
Console.WriteLine("Cleanup complete");
//allow main to run off
exitSystem = true;
//shutdown right away so there are no lingering threads
Environment.Exit(-1);
return true;
#endregion
static void Main(string[] args)
// Some biolerplate to react to close window event, CTRL-C, kill, etc
_handler += new EventHandler(Handler);
SetConsoleCtrlHandler(_handler, true);
//start your multi threaded program here
Program p = new Program();
p.Start();
//hold the console so it doesn’t run off the end
while (!exitSystem)
Thread.Sleep(500);
public void Start()
// start a thread and start doing some processing
Console.WriteLine("Thread started, processing..");
【讨论】:
这个 p/invokes 因此不是跨平台的 我只是加了这个,因为它是解决完整答案的唯一答案。这个是彻底的,允许您在任务计划程序下运行程序,而无需用户参与。你仍然有机会清理。在你的项目中使用 NLOG,你就有了一些可管理的东西。我想知道它是否会在 .NET Core 2 或 3 中编译。【参考方案5】:这个问题非常类似于:
Capture console exit C#
这是我解决这个问题的方法,并处理了用户按 X 和 Ctrl-C 的问题。注意 ManualResetEvents 的使用。这些将导致主线程休眠,从而释放 CPU 以在等待退出或清理时处理其他线程。注意:有必要在 main 结束时设置 TerminationCompletedEvent。如果不这样做,则会由于操作系统在终止应用程序时超时而导致不必要的终止延迟。
namespace CancelSample
using System;
using System.Threading;
using System.Runtime.InteropServices;
internal class Program
/// <summary>
/// Adds or removes an application-defined HandlerRoutine function from the list of handler functions for the calling process
/// </summary>
/// <param name="handler">A pointer to the application-defined HandlerRoutine function to be added or removed. This parameter can be NULL.</param>
/// <param name="add">If this parameter is TRUE, the handler is added; if it is FALSE, the handler is removed.</param>
/// <returns>If the function succeeds, the return value is true.</returns>
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(ConsoleCloseHandler handler, bool add);
/// <summary>
/// The console close handler delegate.
/// </summary>
/// <param name="closeReason">
/// The close reason.
/// </param>
/// <returns>
/// True if cleanup is complete, false to run other registered close handlers.
/// </returns>
private delegate bool ConsoleCloseHandler(int closeReason);
/// <summary>
/// Event set when the process is terminated.
/// </summary>
private static readonly ManualResetEvent TerminationRequestedEvent;
/// <summary>
/// Event set when the process terminates.
/// </summary>
private static readonly ManualResetEvent TerminationCompletedEvent;
/// <summary>
/// Static constructor
/// </summary>
static Program()
// Do this initialization here to avoid polluting Main() with it
// also this is a great place to initialize multiple static
// variables.
TerminationRequestedEvent = new ManualResetEvent(false);
TerminationCompletedEvent = new ManualResetEvent(false);
SetConsoleCtrlHandler(OnConsoleCloseEvent, true);
/// <summary>
/// The main console entry point.
/// </summary>
/// <param name="args">The commandline arguments.</param>
private static void Main(string[] args)
// Wait for the termination event
while (!TerminationRequestedEvent.WaitOne(0))
// Something to do while waiting
Console.WriteLine("Work");
// Sleep until termination
TerminationRequestedEvent.WaitOne();
// Print a message which represents the operation
Console.WriteLine("Cleanup");
// Set this to terminate immediately (if not set, the OS will
// eventually kill the process)
TerminationCompletedEvent.Set();
/// <summary>
/// Method called when the user presses Ctrl-C
/// </summary>
/// <param name="reason">The close reason</param>
private static bool OnConsoleCloseEvent(int reason)
// Signal termination
TerminationRequestedEvent.Set();
// Wait for cleanup
TerminationCompletedEvent.WaitOne();
// Don't run other handlers, just exit.
return true;
【讨论】:
【参考方案6】:Console.TreatControlCAsInput = true;
为我工作。
【讨论】:
这可能会导致 ReadLine 需要为每个输入按两次 Enter 键。【参考方案7】:我可以在退出前进行一些清理工作。这样做的最佳方法是什么 这才是真正的目标:陷阱出口,制作自己的东西。上面的答案都没有正确。因为,Ctrl+C 只是退出应用的众多方式之一。
dotnet c# 中需要什么 - 所谓的 取消令牌 传递给 Host.RunAsync(ct)
,然后,在退出信号陷阱中,对于 Windows,它将是
private static readonly CancellationTokenSource cts = new CancellationTokenSource();
public static int Main(string[] args)
// For gracefull shutdown, trap unload event
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
cts.Cancel();
exitEvent.Wait();
;
Console.CancelKeyPress += (sender, e) =>
cts.Cancel();
exitEvent.Wait();
;
host.RunAsync(cts);
Console.WriteLine("Shutting down");
exitEvent.Set();
return 0;
...
【讨论】:
【参考方案8】:检测 SIGTERM 和 ctrl+c:
CancellationTokenSource ctSource = new();
CancellationToken ct = ctSource.Token;
void ExitHandler()
// You can add any arbitrary global clean up
Console.WriteLine("Exiting...");
ctSource.Cancel();
// Assign exit handler to be called when the process is terminated
// or the user hits CTRL+C
AppDomain.CurrentDomain.ProcessExit += (sender, args) => ExitHandler();
Console.CancelKeyPress += (sender, args) => ExitHandler();
// Then you can use the cancellation token to check for exit:
Console.WriteLine("Ready to gracefully shut down!");
while (!ct.IsCancellationRequested)
Console.WriteLine($"Exit not detected, waiting another 10s.");
Task.Delay(10000, ct).Wait(ct);
【讨论】:
不正确。这个页面上写的解决方案都不能在 LINUX 上运行。 Console.In.Peek()、CurrentDomain.blah blah()、Console.CancelKeyPress() 它们都只能在 Windows 中工作,而根本不能在 linux 中工作。这听起来很简单,但是当您的应用程序作为基于控制台的应用程序在容器中运行时,这是一个大问题,因为 kubernetes 必须杀死它而不是优雅地结束它 我的答案是用 .net core 5 编写的,并测试了我的主要操作系统 Ubuntu 21.10。 这里也一样,我告诉你它根本不起作用。我正在使用 ConsoleHost Console.CancelKeyPress 自 2022 年起为我工作。Win11、.NET 6、NativeAOT 编译的应用程序、OutputType = Exe。【参考方案9】:在我的例子中,我将一个异步 lambda 传递给 Console.CancelKeyPress
,但这是行不通的。
【讨论】:
以上是关于如何在 C# 控制台应用程序中捕获 ctrl-c (SIGINT)的主要内容,如果未能解决你的问题,请参考以下文章