C # 콘솔 앱에서 ctrl-c (SIGINT)를 잡는 방법


221

종료하기 전에 정리를 수행 할 수 있도록 C # 콘솔 응용 프로그램에서 CTRL+ 를 트랩 할 수 있기를 원합니다 C. 가장 좋은 방법은 무엇입니까?

답변:


126

6
실제로이 기사는 P / Invoke를 권장 CancelKeyPress하며 주석에는 간단히 언급되어 있습니다. 좋은 기사는 codeneverwritten.com/2006/10/…
bzlm

이것은 작동하지만 X로 창을 닫지 않습니다. 아래의 전체 솔루션을 참조하십시오. kill도 작동합니다
JJ_Coder4Hire

1
콘솔이 닫히면 Console.CancelKeyPress가 작동을 멈췄습니다. systemd를 사용하여 모노 / 리눅스에서 앱을 실행하거나 앱이 "mono myapp.exe </ dev / null"로 실행되면 SIGINT가 기본 신호 처리기로 전송되어 앱이 즉시 종료됩니다. Linux 사용자는 stackoverflow.com/questions/6546509/…
tekHedd 1

2
@ bzlm의 의견에 대한 링크를 비 깨진 web.archive.org/web/20110424085511/http://...
이안 켐프

@aku 프로세스 무례가 중단되기 전에 SIGINT에 응답해야하는 시간이 없습니까? 어디서나 찾을 수 없으며 내가 읽은 곳을 기억하려고합니다.
John Zabroski

224

이를 위해 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.Canceltrue로 설정되어 있으며, 이는 델리게이트 후에도 계속 실행됨을 의미합니다. 실행되면 프로그램은 사용자가 Ctrl + C를 누를 때까지 기다립니다.이 경우 keepRunning변수가 값을 변경하여 while 루프가 종료됩니다. 이것은 프로그램을 정상적으로 종료하는 방법입니다.


21
keepRunning표시가 필요할 수 있습니다 volatile. 그렇지 않으면 주 스레드가이를 CPU 레지스터에 캐시 할 수 있으며 델리게이트가 실행될 때 값이 변경되지 않습니다.
cdhowie

이것은 작동하지만 X로 창을 닫지 않습니다. 아래의 전체 솔루션을 참조하십시오. kill 과도 작동합니다.
JJ_Coder4Hire

13
에서 ManualResetEvent회전 하기 보다는 대신 사용하도록 변경해야합니다 bool.
Mizipzor

Git-Bash, MSYS2 또는 CygWin에서 실행중인 다른 사람들을위한 작은주의 사항 :이 작업을 수행하려면 winpty를 통해 dotnet을 실행해야합니다 (So, winpty dotnet run). 그렇지 않으면 대리자가 실행되지 않습니다.
Samuel Lampa

CancelKeyPress에 대한 이벤트는 스레드 풀 스레드에서 처리되며 이는 즉시 명백하지 않습니다. docs.microsoft.com/ko-kr/dotnet/api/…
Sam Rueby

100

Jonas의 답변 에 추가하고 싶습니다 . 를 돌리면 boolCPU 사용률이 100 %가되고 CTRL+ 를 기다리는 동안 많은 일을하지 않아도 C됩니다.

더 나은 해결책은 a 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();
}

28
요점은 while 루프 내에서 모든 작업을 수행하고 Ctrl + C를 누르는 동안 while 반복 중에 중단되지 않는다는 것입니다. 시작하기 전에 해당 반복을 완료합니다.
pkr298

2
@ pkr298-너무 나쁜 사람들은 완전히 사실이기 때문에 귀하의 의견에 투표하지 않습니다. 나는 (조나스는 그의 대답 의미가 아니라로서 본질적으로 나쁘지 않다) 조나단이 그런 식으로 생각하는 사람들을 명확히하기 위해 조나스에게 그의 대답을 편집 할 수 있습니다
M. Mimpen을

이 개선 사항으로 기존 답변을 업데이트하십시오.
Mizipzor

27

다음은 완전한 작업 예입니다. 빈 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..");
        }
    }
}

8
따라서이 p /
invokes

나는 이것이 완전한 답을 다루는 유일한 대답이기 때문에 이것을 플러스했습니다. 이것은 철저하고 사용자 개입없이 작업 스케줄러에서 프로그램을 실행할 수 있습니다. 여전히 정리할 기회가 있습니다. 프로젝트에서 NLOG를 사용하면 관리 할 수있는 것이 있습니다. 이 .NET 코어 2 또는 3으로 컴파일 있을지 궁금하다
BigTFromAZ

7

이 질문은 다음과 매우 유사합니다.

콘솔 종료 C # 캡처

다음은이 문제를 해결하고 Ctrl-C뿐만 아니라 X를 누르는 사용자를 처리하는 방법입니다. ManualResetEvents의 사용에 주목하십시오. 이로 인해 메인 스레드가 휴면 상태가되어 종료 또는 정리를 기다리는 동안 CPU가 다른 스레드를 처리 할 수 ​​있습니다. 참고 : 메인 종료시 TerminationCompletedEvent를 설정해야합니다. 그렇지 않으면 응용 프로그램을 종료하는 동안 OS 시간이 초과되어 종료에 불필요한 대기 시간이 발생합니다.

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;
        }
    }
}

3

Console.TreatControlCAsInput = true; 나를 위해 일했다.


2
이로 인해 ReadLine은 각 입력에 대해 두 개의 Enter 프레스를 요구할 수 있습니다.
Grault

0

종료하기 전에 정리를 수행 할 수 있습니다. 이 작업을 수행하는 가장 좋은 방법은 실제 목표입니다. 트랩 종료, 자신 만의 물건 만들기. 그리고 neigther는 위의 대답을 올바르게하지 않습니다. 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;
     }

...

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.