我希望能够在c#控制台应用程序中捕获Ctrl+C,以便在退出之前执行一些清理。做这件事最好的方法是什么?
当前回答
控制台。TreatControlCAsInput = true;对我很有效。
其他回答
这个问题非常类似于:
捕获控制台出口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;
}
}
}
控制台。TreatControlCAsInput = true;对我很有效。
看到MSDN:
控制台。CancelKeyPress事件
带有代码示例的文章:
Ctrl-C和.NET控制台应用程序
控制台。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,这意味着在委托之后继续执行。如果运行,程序等待用户按Ctrl+C。当这种情况发生时,keepprunning变量会改变值,从而导致while循环退出。这是一种使程序优雅退出的方法。
检测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);
}