Tangkap keluar konsol C #

93

Saya memiliki aplikasi konsol yang berisi cukup banyak utas. Ada utas yang memantau kondisi tertentu dan menghentikan program jika benar. Penghentian ini bisa terjadi kapan saja.

Saya memerlukan acara yang dapat dipicu saat program ditutup sehingga saya dapat membersihkan semua utas lainnya dan menutup semua pegangan file dan koneksi dengan benar. Saya tidak yakin apakah sudah ada yang dibangun ke dalam kerangka .NET, jadi saya bertanya sebelum saya menulisnya sendiri.

Saya bertanya-tanya apakah ada acara di sepanjang baris:

MyConsoleProgram.OnExit += CleanupBeforeExit;
ZeroKelvin
sumber
2
Saya tahu ini adalah komentar yang sangat terlambat tetapi Anda tidak perlu melakukannya jika "menutup file & koneksi" adalah satu-satunya hal yang ingin Anda lakukan sebagai pembersihan. Karena Windows sudah menutup semua pegangan yang terkait dengan proses selama penghentian.
Sedat Kapanoglu
6
^ Hanya jika sumber daya tersebut dimiliki oleh proses yang dihentikan. Ini mutlak diperlukan, jika misalnya, Anda mengotomatiskan aplikasi COM yang tersembunyi (katakanlah, Word, atau Excel) di latar belakang, dan Anda perlu memastikan untuk mematikannya sebelum aplikasi Anda keluar, dll.
BrainSlugs83
1
ini memiliki jawaban singkat stackoverflow.com/questions/2555292/…
barlop

Jawaban:

97

Saya tidak yakin di mana saya menemukan kode di web, tetapi sekarang saya menemukannya di salah satu proyek lama saya. Ini akan memungkinkan Anda untuk melakukan kode pembersihan di konsol Anda, misalnya ketika tiba-tiba ditutup atau karena shutdown ...

[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)
{
  switch (sig)
  {
      case CtrlType.CTRL_C_EVENT:
      case CtrlType.CTRL_LOGOFF_EVENT:
      case CtrlType.CTRL_SHUTDOWN_EVENT:
      case CtrlType.CTRL_CLOSE_EVENT:
      default:
          return false;
  }
}


static void Main(string[] args)
{
  // Some biolerplate to react to close window event
  _handler += new EventHandler(Handler);
  SetConsoleCtrlHandler(_handler, true);
  ...
}

Memperbarui

Bagi mereka yang tidak memeriksa komentar, tampaknya solusi khusus ini tidak berfungsi dengan baik (atau sama sekali) di Windows 7 . Utas berikut berbicara tentang ini

flq
sumber
4
Bisakah Anda menggunakan ini untuk membatalkan jalan keluar? Selain saat dimatikan!
ingh. Pagi
7
Ini berfungsi dengan baik, hanya bool Handler()harus return false;(tidak mengembalikan apa pun dalam kode) sehingga akan berhasil. Jika mengembalikan true, windows meminta dialog "Hentikan Proses Sekarang". = D
Cipi
3
Sepertinya solusi ini tidak bekerja dengan Windows 7 untuk acara shutdown, lihat social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB
3
Ketahuilah bahwa jika Anda meletakkan breakpoint dalam metode 'Handler', NullReferenceException akan muncul. Diperiksa di VS2010, Windows 7.
Maxim
10
Ini bekerja sangat baik untuk saya di Windows 7 (64-bit). Tidak yakin mengapa semua orang mengatakan tidak. Satu-satunya modifikasi besar yang saya buat adalah menghilangkan enum dan pernyataan switch, dan untuk "mengembalikan false" dari metode - Saya melakukan semua pembersihan di tubuh metode.
BrainSlugs83
25

Contoh kerja penuh, bekerja dengan ctrl-c, menutup jendela dengan X dan mematikan:

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 boilerplate 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..");
        }
    }
}
JJ_Coder4Hire
sumber
2
Saya menguji ini di windows 7 dengan semua yang dikomentari Handlerkecuali untuk return truedan sementara loop untuk menghitung detik. Aplikasi terus berjalan pada ctrl-c tetapi ditutup setelah 5 detik ketika ditutup dengan X.
Antonios Hadjigeorgalis
Saya minta maaf tetapi dengan menggunakan kode ini saya bisa mendapatkan "Pembersihan selesai" hanya jika saya menekan Ctrl + C, bukan jika saya menutup dengan tombol 'X'; dalam kasus terakhir saya hanya mendapatkan "Sistem keluar karena CTRL-C eksternal, atau proses mematikan, atau mematikan" tetapi kemudian tampaknya konsol menutup sebelum menjalankan bagian Handlermetode yang tersisa {menggunakan Win10, .NET Framework 4.6.1}
Giacomo Pirinoli
pada windows 10 bekerja untuk saya CTRL-C, X pada jendela DAN proses akhiri di Task Manager.
JJ_Coder4Hire
8

Periksa juga:

AppDomain.CurrentDomain.ProcessExit
jmservera
sumber
7
Ini hanya muncul untuk menangkap keluar dari kembali atau Lingkungan. Keluar, itu tidak menangkap CTRL + C, CTRL + Break, atau tombol tutup yang sebenarnya di konsol.
Kit10
Jika Anda menangani CTRL + C secara terpisah menggunakan Console.CancelKeyPressmaka ProcessExitacara benar-benar dimunculkan setelah semua CancelKeyPresseksekusi penangan acara.
Konard
5

Saya memiliki masalah serupa, hanya Aplikasi konsol saya yang akan berjalan dalam putaran tak terbatas dengan satu pernyataan preemptive di tengah. Inilah solusi saya:

class Program
{
    static int Main(string[] args)
    {
        // Init Code...
        Console.CancelKeyPress += Console_CancelKeyPress;  // Register the function to cancel event

        // I do my stuffs

        while ( true )
        {
            // Code ....
            SomePreemptiveCall();  // The loop stucks here wating function to return
            // Code ...
        }
        return 0;  // Never comes here, but...
    }

    static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
    {
        Console.WriteLine("Exiting");
        // Termitate what I have to terminate
        Environment.Exit(-1);
    }
}
João Portela
sumber
4

Sepertinya Anda memiliki utas yang langsung menghentikan aplikasi? Mungkin akan lebih baik jika thread memberi sinyal pada thread utama untuk mengatakan bahwa aplikasi tersebut harus dihentikan.

Saat menerima sinyal ini, utas utama dapat mematikan utas lainnya dengan bersih dan akhirnya menutup sendiri.

rampok
sumber
3
Saya harus setuju dengan jawaban ini. Memaksa keluar dari aplikasi dan kemudian mencoba membersihkan setelahnya bukanlah cara yang tepat. Kontrol aplikasi Anda, Noit. Jangan biarkan itu mengendalikan Anda.
Randolpho
1
Sebuah utas yang saya buat secara langsung bukanlah satu-satunya hal yang dapat menutup aplikasi saya. Ctrl-C dan "tombol tutup" adalah cara lain untuk mengakhiri ini. Kode yang diposting oleh Frank, setelah sedikit modifikasi, sangat cocok.
ZeroKelvin
4

Jawaban ZeroKelvin berfungsi di aplikasi konsol Windows 10 x64, .NET 4.6. Bagi mereka yang tidak perlu berurusan dengan CtrlType enum, berikut adalah cara yang sangat sederhana untuk menghubungkan ke shutdown framework:

class Program
{
    private delegate bool ConsoleCtrlHandlerDelegate(int sig);

    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(ConsoleCtrlHandlerDelegate handler, bool add);

    static ConsoleCtrlHandlerDelegate _consoleCtrlHandler;

    static void Main(string[] args)
    {
        _consoleCtrlHandler += s =>
        {
            //DoCustomShutdownStuff();
            return false;   
        };
        SetConsoleCtrlHandler(_consoleCtrlHandler, true);
    }
}

Mengembalikan FALSE dari penangan memberi tahu kerangka kerja bahwa kita tidak "menangani" sinyal kontrol, dan fungsi penangan berikutnya dalam daftar penangan untuk proses ini digunakan. Jika tidak ada penangan yang mengembalikan TRUE, penangan default dipanggil.

Perhatikan bahwa ketika pengguna melakukan logoff atau shutdown, callback tidak dipanggil oleh Windows tetapi dihentikan segera.

BCA
sumber
3

Ada untuk aplikasi WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Untuk aplikasi Konsol, coba

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Tetapi saya tidak yakin pada poin apa yang dipanggil atau apakah itu akan berfungsi dari dalam domain saat ini. Saya kira tidak.

Rob Prouse
sumber
Dokumen bantuan untuk DomainUnload mengatakan "Delegasi EventHandler untuk acara ini dapat melakukan aktivitas penghentian sebelum domain aplikasi diturunkan." Jadi sepertinya itu berfungsi dalam domain saat ini. Namun, ini mungkin tidak berfungsi untuk kebutuhannya karena utasnya dapat membuat domain tetap aktif.
Rob Parker
2
Ini hanya menangani CTRL + C dan CTRL + Close, itu tidak menangkap ada melalui kembali, Lingkungan Keluar atau mengklik tombol tutup.
Kit10
Tidak menangkap CTRL + C untuk saya dengan Mono di Linux.
starbeamrainbowlabs
2

Visual Studio 2015 + Windows 10

  • Izinkan untuk pembersihan
  • Aplikasi instance tunggal
  • Beberapa lempengan emas

Kode:

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace YourNamespace
{
    class Program
    {
        // if you want to allow only one instance otherwise remove the next line
        static Mutex mutex = new Mutex(false, "YOURGUID-YOURGUID-YOURGUID-YO");

        static ManualResetEvent run = new ManualResetEvent(true);

        [DllImport("Kernel32")]
        private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);                
        private delegate bool EventHandler(CtrlType sig);
        static EventHandler exitHandler;
        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 ExitHandler(CtrlType sig)
        {
            Console.WriteLine("Shutting down: " + sig.ToString());            
            run.Reset();
            Thread.Sleep(2000);
            return false; // If the function handles the control signal, it should return TRUE. If it returns FALSE, the next handler function in the list of handlers for this process is used (from MSDN).
        }


        static void Main(string[] args)
        {
            // if you want to allow only one instance otherwise remove the next 4 lines
            if (!mutex.WaitOne(TimeSpan.FromSeconds(2), false))
            {
                return; // singleton application already started
            }

            exitHandler += new EventHandler(ExitHandler);
            SetConsoleCtrlHandler(exitHandler, true);

            try
            {
                Console.BackgroundColor = ConsoleColor.Gray;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.Clear();
                Console.SetBufferSize(Console.BufferWidth, 1024);

                Console.Title = "Your Console Title - XYZ";

                // start your threads here
                Thread thread1 = new Thread(new ThreadStart(ThreadFunc1));
                thread1.Start();

                Thread thread2 = new Thread(new ThreadStart(ThreadFunc2));
                thread2.IsBackground = true; // a background thread
                thread2.Start();

                while (run.WaitOne(0))
                {
                    Thread.Sleep(100);
                }

                // do thread syncs here signal them the end so they can clean up or use the manual reset event in them or abort them
                thread1.Abort();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("fail: ");
                Console.ForegroundColor = ConsoleColor.Black;
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                {
                    Console.WriteLine("Inner: " + ex.InnerException.Message);
                }
            }
            finally
            {                
                // do app cleanup here

                // if you want to allow only one instance otherwise remove the next line
                mutex.ReleaseMutex();

                // remove this after testing
                Console.Beep(5000, 100);
            }
        }

        public static void ThreadFunc1()
        {
            Console.Write("> ");
            while ((line = Console.ReadLine()) != null)
            {
                if (line == "command 1")
                {

                }
                else if (line == "command 1")
                {

                }
                else if (line == "?")
                {

                }

                Console.Write("> ");
            }
        }


        public static void ThreadFunc2()
        {
            while (run.WaitOne(0))
            {
                Thread.Sleep(100);
            }

           // do thread cleanup here
            Console.Beep();         
        }

    }
}
AJBauer
sumber
Menarik bahwa ini sepertinya jawaban yang paling kuat. Namun, berhati-hatilah saat mengubah ukuran buffer konsol Anda: jika tinggi buffer kurang dari tinggi jendela, program akan mengeluarkan pengecualian saat startup.
John Zabroski
1

The Link yang disebutkan di atas oleh Charle B dalam komentar untuk flq

Jauh di lubuk hatinya mengatakan:

SetConsoleCtrlHandler tidak akan berfungsi di windows7 jika Anda menautkan ke user32

Beberapa tempat lain di utas disarankan untuk membuat jendela tersembunyi. Jadi saya membuat winform dan dalam onload saya terpasang ke konsol dan menjalankan Main asli. Dan kemudian SetConsoleCtrlHandle berfungsi dengan baik (SetConsoleCtrlHandle dipanggil seperti yang disarankan oleh flq)

public partial class App3DummyForm : Form
{
    private readonly string[] _args;

    public App3DummyForm(string[] args)
    {
        _args = args;
        InitializeComponent();
    }

    private void App3DummyForm_Load(object sender, EventArgs e)
    {
        AllocConsole();
        App3.Program.OriginalMain(_args);
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool AllocConsole();
}
Jens
sumber
Sebenarnya ini tidak berhasil. Saya memiliki aplikasi WFP multi-jendela dan saya menggunakan konsol ( AllocConsoleseperti dalam contoh Anda) untuk menampilkan beberapa informasi tambahan. Masalahnya adalah bahwa seluruh aplikasi (semua Windows) ditutup jika pengguna mengklik (X) di Jendela Konsol. The SetConsoleCtrlHandlerbekerja, tapi pemberhentian aplikasi pula sebelum kode di pawang dieksekusi (saya melihat breakpoints dipecat dan benar maka pemberhentian aplikasi).
Mike Keskinov
Tetapi saya menemukan solusi yang bekerja untuk saya - Saya sederhana tombol tutup DISABLED . Lihat: stackoverflow.com/questions/6052992/…
Mike Keskinov
0

Bagi yang tertarik dengan VB.net. (Saya mencari di internet dan tidak dapat menemukan padanan untuk itu) Ini diterjemahkan ke dalam vb.net.

    <DllImport("kernel32")> _
    Private Function SetConsoleCtrlHandler(ByVal HandlerRoutine As HandlerDelegate, ByVal Add As Boolean) As Boolean
    End Function
    Private _handler As HandlerDelegate
    Private Delegate Function HandlerDelegate(ByVal dwControlType As ControlEventType) As Boolean
    Private Function ControlHandler(ByVal controlEvent As ControlEventType) As Boolean
        Select Case controlEvent
            Case ControlEventType.CtrlCEvent, ControlEventType.CtrlCloseEvent
                Console.WriteLine("Closing...")
                Return True
            Case ControlEventType.CtrlLogoffEvent, ControlEventType.CtrlBreakEvent, ControlEventType.CtrlShutdownEvent
                Console.WriteLine("Shutdown Detected")
                Return False
        End Select
    End Function
    Sub Main()
        Try
            _handler = New HandlerDelegate(AddressOf ControlHandler)
            SetConsoleCtrlHandler(_handler, True)
     .....
End Sub
dko
sumber
Solusi di atas tidak bekerja untuk saya vb.net 4.5 kerangka ControlEventType tidak terselesaikan. Saya dapat menggunakan ide ini sebagai solusi stackoverflow.com/questions/15317082/…
glant