C# - Console 프로그램이 Ctrl+C 종료 시점을 감지하는 방법
콘솔 프로그램에서, 일례로 Ctrl+C를 눌러 작업을 종료하는 경우가 있습니다. 그 시점에, 특정 코드를 실행해야 한다면 어떤 처리를 해야 할까요?
이에 대해 검색해 보면,
Detecting console application exit in c#
; https://social.msdn.microsoft.com/Forums/vstudio/en-US/707e9ae1-a53f-4918-8ac4-62a1eddb3c4a/detecting-console-application-exit-in-c?forum=csharpgeneral
Win32 API의 SetConsoleCtrlHandler를 사용하라고 나오는데요, 아쉽게도 이것은 저 글의 특수한 예제에서나 감지될 뿐 일반적으로는 그렇게 설치한 핸들러의 실행이 완료되기도 전에 종료합니다.
실제로 다음과 같이 예제를 만들어,
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
// Put your own handler here
switch (ctrlType)
{
case CtrlTypes.CTRL_C_EVENT:
Console.WriteLine("CTRL+C received!");
break;
case CtrlTypes.CTRL_BREAK_EVENT:
Console.WriteLine("CTRL+BREAK received!");
break;
case CtrlTypes.CTRL_CLOSE_EVENT:
Console.WriteLine("Program being closed!");
break;
case CtrlTypes.CTRL_LOGOFF_EVENT:
case CtrlTypes.CTRL_SHUTDOWN_EVENT:
Console.WriteLine("User is logging off!");
break;
}
return true;
}
static void Main(string[] args)
{
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
// Console.CancelKeyPress += Console_CancelKeyPress;
Console.ReadLine();
}
// private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
// {
// Console.WriteLine("Console_CancelKeyPress");
// }
}
}
실행 후, Ctrl+C 키를 누르면 화면에 아무것도 안 찍힙니다. 대신 디버깅 상태에서 ConsoleCtrlCheck 메서드의 도입부에 BP를 찍으면 걸리는 것을 확인할 수 있지만 이후 실행을 하게 되면 해당 메서드를 모두 수행하기도 전에 프로세스가 종료됩니다. (즉, ConsoleCtrlCheck 메서드는 다른 스레드에 의해 실행됩니다.)
아쉽지만, Ctrl+C에 무관하게 종료 시점을 알 수 있는 방법은 있습니다. 다름 아닌, AppDomain의 ProcessExit 이벤트를 구독하는 것입니다.
static void Main(string[] args)
{
// DomainUnload는 소용 없고,
// AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
Console.CancelKeyPress += Console_CancelKeyPress;
Console.ReadLine();
}
private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
Console.WriteLine("CurrentDomain_ProcessExit");
}
이렇게 만들고 실행 후 Ctrl+C 키를 누르면 화면에 CurrentDomain_ProcessExit 메시지가 찍히는 것을 확인할 수 있습니다.
AppDomain.ProcessExit 이벤트가 좋은 점이 있다면, Environment.Exit 메서드의 호출로 인한 종료 상황에서도 잘 동작한다는 것입니다.
static void Main(string[] args)
{
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
ThreadPool.QueueUserWorkItem(
(arg) =>
{
Thread.Sleep(5000);
Environment.Exit(5);
}
, null);
Console.ReadLine();
}
만약, Ctrl+C 이벤트를 꼭 받아서 처리해야 한다면 반드시 Foregroud 유형의 스레드 하나는 살아 있도록 해야 합니다. "
Detecting console application exit in c#" 글의 예제가 잘 동작했던 것은 마지막에 while 문으로 Main 스레드를 살아 있도록 유지했기 때문입니다.
현실적으로 저렇게 운영하는 것은 좀 그렇고 Ctrl+C 핸들러 실행을 완료할 수 있도록 별도의 스레드 하나를 Foreground로 운영하는 것이 더 좋습니다.
static void Main(string[] args)
{
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
Thread t1 = new Thread(() =>
{
while (true) Thread.Sleep(1000);
});
t1.Start();
Console.ReadLine();
}
위와 같이 처리해 주면, Ctrl+C를 눌렀을 때 정상적으로 화면에 "CTRL+C received!" 메시지가 찍히는 것을 볼 수 있고, 부가적으로 ConsoleCtrlCheck 메서드가 할 일을 다했다면 그 사실을 Foreground 스레드에 알려 해당 스레드가 종료하도록 만들면 됩니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]