Bir C # konsol uygulamasında ctrl-c'yi (SIGINT) nasıl yakalarım


222

Bir C # konsol uygulamasında CTRL+ yakalayabilmek Cistiyorum, böylece çıkmadan önce bazı temizlik işlemleri yapabilirim. Bunu yapmanın en iyi yolu nedir?

Yanıtlar:


127

MSDN'ye bakın:

Console.CancelKeyPress Etkinliği

Kod örnekleri içeren makale:

Ctrl-C ve .NET konsol uygulaması


6
Aslında, bu makalede P / Invoke önerilmektedir CancelKeyPressve yorumlarda sadece kısaca bahsedilmektedir. İyi bir makale codeneverwritten.com/2006/10/…
bzlm

Bu çalışıyor, ancak pencerenin X ile kapanmasını engellemiyor. Aşağıdaki eksiksiz çözümüme bakın. kill as ile de çalışır
JJ_Coder4Hire

1
Konsol kapalı ise, Console.CancelKeyPress'in çalışmayı durduracağını gördüm. Bir uygulamayı systemd ile mono / linux altında çalıştırıyorsanız veya uygulama "mono myapp.exe </ dev / null" olarak çalıştırılırsa, varsayılan sinyal işleyiciye bir SIGINT gönderilir ve anında uygulama öldürülür. Linux kullanıcıları stackoverflow.com/questions/6546509/…
tekHedd

2
@ Bzlm adlı kullanıcının yorumu için bozuk bağlantı: web.archive.org/web/20110424085511/http://…
Ian Kemp

@aku, işlem kaba iptallerinden önce SIGINT'e yanıt vermek için belirli bir süre yok mu? Hiçbir yerde bulamıyorum ve nerede okuduğumu hatırlamaya çalışıyorum.
John Zabroski

224

Bunun için Console.CancelKeyPress olayı kullanılır. Bu şekilde kullanılır:

public static void Main(string[] args)
{
    Console.CancelKeyPress += delegate {
        // call methods to clean up
    };

    while (true) {}
}

Kullanıcı Ctrl + C tuşlarına bastığında, temsilci içindeki kod çalıştırılır ve programdan çıkar. Bu, necessairy yöntemlerini çağırarak temizleme gerçekleştirmenizi sağlar. Temsilci çalıştırıldıktan sonra hiçbir kod olmadığını unutmayın.

Bunun kesmeyeceği başka durumlar da var. Örneğin, program şu anda hemen durdurulamayan önemli hesaplamalar yapıyorsa. Bu durumda, doğru strateji, hesaplama tamamlandıktan sonra programdan çıkılmasını söylemek olabilir. Aşağıdaki kod, bunun nasıl uygulanabileceğine ilişkin bir örnek vermektedir:

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

Bu kod ile ilk örnek arasındaki fark e.Cancel, true olarak ayarlanmış olmasıdır; Çalıştırılırsa, program kullanıcının Ctrl + C tuşlarına basmasını bekler. Bu durumda keepRunningdeğişken, while döngüsünün çıkmasına neden olan değeri değiştirir. Bu programın zarif bir şekilde çıkmasını sağlamanın bir yoludur.


21
keepRunningişaretlenmesi gerekebilir volatile. Ana iş parçacığı bunu bir CPU kaydında önbelleğe alabilir ve temsilci yürütüldüğünde değer değişikliğini fark etmez.
cdhowie

Bu çalışıyor, ancak pencerenin X ile kapanmasını engellemiyor. Aşağıdaki eksiksiz çözümüme bakın. öldürmekle de çalışır.
JJ_Coder4Hire

13
A ManualResetEventyerine döndürmek yerine değiştirilmelidir bool.
Mizipzor

Git-Bash, MSYS2 veya CygWin'de koşu yapan herkes için küçük bir uyarı: Bunun çalışması için dotnet'i winpty ile çalıştırmanız gerekecek (Yani, winpty dotnet run). Aksi takdirde, temsilci asla çalıştırılmaz.
Samuel Lampa

Dikkat et - CancelKeyPress olayı, hemen belirgin olmayan bir iş parçacığı havuzu iş parçacığında işlenir: docs.microsoft.com/en-us/dotnet/api/…
Sam Rueby

100

Jonas'ın cevabına eklemek istiyorum . Bir eğirme, bool% 100 CPU kullanımına neden olur ve CTRL+ için beklerken bir sürü enerji harcamaz C.

Daha iyi bir çözüm, ManualResetEventaslında CTRL+ için "beklemek" için a kullanmaktır 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
Asıl mesele, while döngüsünün içindeki tüm işleri yapacağınız ve Ctrl + C'ye basmanın süre yinelemesinin ortasında kırılmayacağıdır; ayrılmadan önce bu yinelemeyi tamamlayacaktır.
pkr298

2
@ pkr298 - Çok kötü insanlar tamamen doğru olduğu için yorumunuzu oylamıyor. İnsanları Jonathon'un nasıl düşündüğünü açıklığa kavuşturmak için Jonas'ın cevabını düzenleyeceğim (doğal olarak kötü değil ama Jonas'ın cevabı anlamına geldiği gibi değil)
M. Mimpen

Bu iyileştirme ile mevcut yanıtı güncelleyin.
Mizipzor

27

İşte tam bir çalışma örneği. boş C # konsol projesine yapıştırın:

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
bu p /
invokes

Bunu sadece artıdım çünkü tam bir cevabı ele alan tek cevap bu. Bu kapsamlı ve programı kullanıcı müdahalesi olmadan görev zamanlayıcı altında çalıştırmak için izin verir. Yine de temizleme şansınız var. Projenizde NLOG kullanın ve yönetilebilir bir şeyiniz var. .NET Core 2 veya 3'te derlenip derlenmeyeceğini merak ediyorum
BigTFromAZ

7

Bu soru şuna çok benzer:

Yakalama konsolu çıkışı C #

İşte bu sorunu nasıl çözdüm ve kullanıcı X yanı sıra Ctrl-C isabet ele. ManualResetEvents kullanmaya dikkat edin. Bunlar, ana iş parçacığının uykuya neden olmasına neden olur; Not: ana sonunda TerminationCompletedEvent ayarlamak gerekir. Bunun yapılmaması, uygulamayı öldürürken işletim sistemi zaman aşımına uğraması nedeniyle sonlandırmanın gereksiz gecikmesine neden olur.

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; benim için çalıştı.


2
Bu, ReadLine'ın her giriş için iki Enter tuşuna basmasına neden olabilir.
Grault

0

Çıkmadan önce bazı temizlik işlemleri yapabilirim. Bunu yapmanın en iyi yolu nedir bu gerçek hedef: tuzak çıkış, kendi şeyler yapmak. Ve yukarıda verilen diğer cevaplar doğru yapmıyor. Çünkü, Ctrl + C uygulamadan çıkmanın birçok yolundan sadece biridir.

Bunun için dotnet c # 'a ne gerek var - iptal belirteci geçti Host.RunAsync(ct)ve sonra çıkış sinyalleri tuzaklarında, Windows için

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

...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.