จับคอนโซลทางออก C #


93

ฉันมีแอปพลิเคชันคอนโซลที่มีเธรดค่อนข้างมาก มีเธรดที่ตรวจสอบเงื่อนไขบางอย่างและยุติโปรแกรมหากเป็นจริง การยุตินี้สามารถเกิดขึ้นได้ทุกเมื่อ

ฉันต้องการเหตุการณ์ที่สามารถทริกเกอร์ได้เมื่อโปรแกรมกำลังปิดเพื่อที่ฉันจะได้ล้างเธรดอื่น ๆ ทั้งหมดและปิดการจัดการไฟล์และการเชื่อมต่อทั้งหมดอย่างถูกต้อง ฉันไม่แน่ใจว่ามีเฟรมเวิร์กใน. NET อยู่แล้วหรือไม่ดังนั้นฉันจึงถามก่อนที่จะเขียนของตัวเอง

ฉันสงสัยว่ามีเหตุการณ์ตามบรรทัดของ:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
ฉันรู้ว่านี่เป็นความคิดเห็นที่ล่าช้ามาก แต่คุณไม่จำเป็นต้องทำเช่นนั้นจริงๆหาก "การปิดไฟล์และการเชื่อมต่อ" เป็นสิ่งเดียวที่คุณต้องการทำเพื่อเป็นการล้างข้อมูล เนื่องจาก Windows ปิดแฮนเดิลทั้งหมดที่เกี่ยวข้องกับกระบวนการในระหว่างการสิ้นสุดแล้ว
Sedat Kapanoglu

6
^ เฉพาะในกรณีที่ทรัพยากรเหล่านั้นเป็นของกระบวนการที่กำลังยุติ นี่เป็นสิ่งที่จำเป็นอย่างยิ่งเช่นคุณกำลังสร้างแอปพลิเคชัน COM ที่ซ่อนอยู่โดยอัตโนมัติ (เช่น Word หรือ Excel) ในพื้นหลังและคุณต้องฆ่ามันก่อนที่แอปของคุณจะออกเป็นต้น
BrainSlugs83

1
มีคำตอบสั้น ๆstackoverflow.com/questions/2555292/…
barlop

คำตอบ:


97

ฉันไม่แน่ใจว่าฉันพบรหัสบนเว็บที่ไหน แต่ตอนนี้ฉันพบแล้วในโครงการเก่าของฉัน สิ่งนี้จะช่วยให้คุณทำการล้างรหัสในคอนโซลของคุณเช่นเมื่อปิดทันทีหรือเนื่องจากการปิดเครื่อง ...

[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);
  ...
}

อัปเดต

สำหรับผู้ที่ไม่ตรวจสอบความคิดเห็นที่ดูเหมือนว่าการแก้ปัญหานี้โดยเฉพาะอย่างยิ่งไม่ได้ทำงานได้ดี (หรือที่ทุกคน) บนWindows 7 กระทู้ต่อไปนี้พูดถึงเรื่องนี้


4
คุณสามารถใช้สิ่งนี้เพื่อยกเลิกการออกได้หรือไม่? นอกเหนือจากตอนที่กำลังปิดตัวลง!
ingh.am

7
วิธีนี้ใช้งานได้ดีเพียงbool Handler()ต้องreturn false;(ไม่คืนค่าอะไรในโค้ด) จึงจะใช้งานได้ หากส่งกลับเป็นจริงหน้าต่างจะแสดงข้อความ "ยุติกระบวนการเดี๋ยวนี้" = D
Cipi

3
ดูเหมือนว่าการแก้ปัญหานี้ไม่ได้ทำงานกับ Windows 7 สำหรับเหตุการณ์ปิดดูsocial.msdn.microsoft.com/Forums/en/windowscompatibility/thread/...
CharlesB

3
โปรดทราบว่าหากคุณใส่เบรกพอยต์ในเมธอด 'Handler' มันจะทำให้เกิด NullReferenceException เช็คอิน VS2010, Windows 7
Maxim

10
สิ่งนี้ใช้ได้ดีสำหรับฉันใน Windows 7 (64 บิต) ไม่แน่ใจว่าทำไมทุกคนถึงไม่พูด การแก้ไขที่สำคัญเพียงอย่างเดียวที่ฉันทำคือการกำจัด enum และเปลี่ยนคำสั่งและเพื่อ "คืนค่าเท็จ" จากวิธีการ - ฉันทำการล้างข้อมูลทั้งหมดในเนื้อหาของวิธีการ
BrainSlugs83

25

ตัวอย่างการทำงานแบบเต็มทำงานร่วมกับ ctrl-c ปิดหน้าต่างด้วย X และฆ่า:

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

2
ฉันทดสอบสิ่งนี้บน windows 7 โดยทุกอย่างที่แสดงความคิดเห็นHandlerยกเว้นreturn trueและ while วนซ้ำเพื่อนับวินาที แอปพลิเคชันยังคงทำงานบน ctrl-c แต่จะปิดหลังจาก 5 วินาทีเมื่อปิดด้วย X
Antonios Hadjigeorgalis

ฉันขอโทษ แต่เมื่อใช้รหัสนี้ฉันจะสามารถ"ล้างข้อมูลเสร็จสมบูรณ์" ได้ก็ต่อเมื่อฉันกด Ctrl + C ไม่ใช่ถ้าฉันปิดด้วยปุ่ม 'X' ในกรณีหลังฉันได้รับเฉพาะ"การออกจากระบบเนื่องจาก CTRL-C ภายนอกหรือการฆ่ากระบวนการหรือการปิดระบบ"แต่ดูเหมือนว่าคอนโซลจะปิดก่อนที่จะดำเนินการส่วนที่เหลือของHandlerวิธีการ {โดยใช้ Win10, .NET Framework 4.6.1}
Giacomo Pirinoli

บน windows 10 ทำงานให้ฉัน CTRL-C, X บนหน้าต่างและสิ้นสุดกระบวนการในตัวจัดการงาน
JJ_Coder4Hire

8

ตรวจสอบด้วย:

AppDomain.CurrentDomain.ProcessExit

7
สิ่งนี้ดูเหมือนจะจับออกจากการส่งคืนหรือสภาพแวดล้อมเท่านั้นออกจะไม่จับ CTRL + C, CTRL + Break หรือปุ่มปิดจริงบนคอนโซล
Kit10

หากคุณจัดการ CTRL + C แยกกันโดยใช้เหตุการณ์Console.CancelKeyPressนั้นProcessExitจะเพิ่มขึ้นจริงหลังจากCancelKeyPressการดำเนินการตัวจัดการเหตุการณ์ทั้งหมด
Konard

5

ฉันมีปัญหาที่คล้ายกันเพียงแค่แอปคอนโซลของฉันจะทำงานแบบวนซ้ำไม่สิ้นสุดโดยมีคำสั่งล่วงหน้าเพียงคำสั่งเดียว นี่คือทางออกของฉัน:

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

4

ดูเหมือนว่าคุณมีเธรดที่ยุติแอปพลิเคชันโดยตรงหรือไม่? บางทีอาจจะเป็นการดีกว่าหากมีเธรดส่งสัญญาณไปยังเธรดหลักเพื่อบอกว่าแอปพลิเคชันควรจะยุติ

ในการรับสัญญาณนี้เธรดหลักสามารถปิดเธรดอื่นได้อย่างหมดจดและปิดตัวลงในที่สุด


3
ฉันต้องเห็นด้วยกับคำตอบนี้ การบังคับให้แอปพลิเคชันออกแล้วพยายามล้างข้อมูลในภายหลังไม่ใช่วิธีที่จะไป ควบคุมแอปพลิเคชันของคุณ Noit อย่าปล่อยให้มันควบคุมคุณ
Randolpho

1
เธรดที่เกิดจากฉันโดยตรงไม่จำเป็นต้องเป็นสิ่งเดียวที่สามารถปิดแอปพลิเคชันของฉันได้ Ctrl-C และ "ปุ่มปิด" เป็นวิธีอื่น ๆ ที่สามารถสิ้นสุดได้ รหัสที่โพสต์โดย Frank หลังจากการปรับเปลี่ยนเล็กน้อยเข้ากันได้ดี
ZeroKelvin

4

คำตอบของ ZeroKelvin ทำงานในแอปคอนโซล Windows 10 x64, .NET 4.6 สำหรับผู้ที่ไม่ต้องการจัดการกับ CtrlType enum นี่เป็นวิธีง่ายๆในการเชื่อมต่อกับการปิดระบบ:

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

การส่งคืน FALSE จากตัวจัดการจะบอกกรอบงานว่าเราไม่ได้ "จัดการ" สัญญาณควบคุมและใช้ฟังก์ชันตัวจัดการถัดไปในรายการตัวจัดการสำหรับกระบวนการนี้ หากไม่มีตัวจัดการใดที่ส่งกลับค่า TRUE ระบบจะเรียกตัวจัดการเริ่มต้น

โปรดทราบว่าเมื่อผู้ใช้ทำการล็อกออฟหรือปิดระบบ Windows จะไม่เรียกการเรียกกลับ แต่จะถูกยกเลิกทันที


3

มีไว้สำหรับแอป WinForms

Application.ApplicationExit += CleanupBeforeExit;

สำหรับแอปคอนโซลลอง

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

แต่ฉันไม่แน่ใจว่าจุดใดที่ถูกเรียกหรือว่าจะใช้งานได้จากภายในโดเมนปัจจุบัน ฉันสงสัยว่าไม่


เอกสารความช่วยเหลือสำหรับ DomainUnload ระบุว่า "ผู้รับมอบสิทธิ์ EventHandler สำหรับเหตุการณ์นี้สามารถดำเนินกิจกรรมการยุติใด ๆ ก่อนที่โดเมนของแอปพลิเคชันจะถูกยกเลิกการโหลด" ดูเหมือนว่ามันจะทำงานภายในโดเมนปัจจุบัน อย่างไรก็ตามอาจไม่ได้ผลสำหรับความต้องการของเขาเนื่องจากเธรดของเขาอาจทำให้โดเมนคงอยู่
Rob Parker

2
สิ่งนี้จัดการเฉพาะ CTRL + C และ CTRL + Close เท่านั้นซึ่งจะไม่สามารถตรวจจับได้ผ่านการส่งคืนสภาพแวดล้อมออกหรือคลิกปุ่มปิด
Kit10

ไม่จับ CTRL + C สำหรับฉันด้วย Mono บน Linux
starbeamrainbowlabs

2

Visual Studio 2015 + Windows 10

  • อนุญาตให้ล้างข้อมูล
  • แอปอินสแตนซ์เดียว
  • ชุบทองบางส่วน

รหัส:

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

    }
}

น่าสนใจว่านี่น่าจะเป็นคำตอบที่แข็งแกร่งที่สุด อย่างไรก็ตามโปรดใช้ความระมัดระวังในการเปลี่ยนขนาดบัฟเฟอร์คอนโซลของคุณ: หากความสูงของบัฟเฟอร์น้อยกว่าความสูงของหน้าต่างโปรแกรมจะแสดงข้อยกเว้นเมื่อเริ่มต้น
John Zabroski

1

การเชื่อมโยงดังกล่าวข้างต้นโดย Charle B ในความคิดเห็นที่ FLQ

ลึกลงไปพูดว่า:

SetConsoleCtrlHandler จะไม่ทำงานบน windows7 หากคุณเชื่อมโยงไปยัง user32

ที่อื่นในเธรดแนะนำให้ใส่หน้าต่างที่ซ่อนอยู่ ดังนั้นฉันจึงสร้าง winform และใน onload ฉันแนบกับคอนโซลและเรียกใช้ Main ดั้งเดิม จากนั้น SetConsoleCtrlHandle ทำงานได้ดี (SetConsoleCtrlHandle เรียกว่าตามคำแนะนำโดย 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();
}

อันที่จริงมันใช้ไม่ได้ ฉันมีแอพ WFP แบบหลายหน้าต่างและฉันใช้คอนโซล ( AllocConsoleตามตัวอย่างของคุณ) เพื่อแสดงข้อมูลเพิ่มเติม ปัญหาคือแอพทั้งหมด (Windows ทั้งหมด) ถูกปิดหากผู้ใช้คลิกที่ (X) บนหน้าต่างคอนโซล ใช้SetConsoleCtrlHandlerงานได้ แต่แอปจะหยุดทำงานก่อนที่โค้ดใด ๆ ในตัวจัดการจะดำเนินการ (ฉันเห็นเบรกพอยต์เริ่มทำงานและแอปหยุดทำงาน)
Mike Keskinov

แต่ฉันพบวิธีแก้ปัญหาที่เหมาะกับฉัน - ฉันปิดการใช้งานปุ่มปิดอย่างง่าย ดู: stackoverflow.com/questions/6052992/…
Mike Keskinov

0

สำหรับผู้ที่สนใจ VB.net (ฉันค้นหาในอินเทอร์เน็ตและไม่พบสิ่งที่เทียบเท่า) ที่นี่มีการแปลเป็น 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

วิธีแก้ปัญหาข้างต้นไม่ได้ผลสำหรับฉัน vb.net 4.5 กรอบ ControlEventType ไม่ได้รับการแก้ไข ฉันสามารถใช้แนวคิดนี้เป็นโซลูชันstackoverflow.com/questions/15317082/…
glant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.