Chụp lối ra bảng điều khiển C #


92

Tôi có một ứng dụng bảng điều khiển có chứa khá nhiều luồng. Có các luồng giám sát các điều kiện nhất định và kết thúc chương trình nếu chúng đúng. Việc chấm dứt này có thể xảy ra bất cứ lúc nào.

Tôi cần một sự kiện có thể được kích hoạt khi chương trình đang đóng để tôi có thể dọn dẹp tất cả các luồng khác và đóng tất cả các kết nối và xử lý tệp đúng cách. Tôi không chắc liệu đã có cái nào được tích hợp sẵn trong .NET framework hay chưa, vì vậy tôi đang hỏi trước khi viết cho riêng mình.

Tôi đã tự hỏi liệu có một sự kiện dọc theo dòng:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
Tôi biết đây là một nhận xét rất muộn nhưng bạn không thực sự cần phải làm điều đó nếu "đóng tệp và kết nối" là điều duy nhất bạn muốn làm là dọn dẹp. Bởi vì Windows đã đóng tất cả các xử lý được liên kết với một quá trình trong quá trình chấm dứt.
Sedat Kapanoglu

6
^ Chỉ khi các tài nguyên đó thuộc sở hữu của quá trình bị chấm dứt. Điều này là hoàn toàn cần thiết, nếu chẳng hạn, bạn đang tự động hóa một ứng dụng COM ẩn (giả sử, Word hoặc Excel) trong nền và bạn cần đảm bảo diệt ứng dụng đó trước khi ứng dụng của bạn thoát, v.v.
BrainSlugs83

1
này có một đoạn ngắn tìm kiếm câu trả lời stackoverflow.com/questions/2555292/...
barlop

Câu trả lời:


96

Tôi không chắc mình đã tìm thấy mã ở đâu trên web, nhưng bây giờ tôi đã tìm thấy nó trong một trong những dự án cũ của mình. Điều này sẽ cho phép bạn thực hiện mã dọn dẹp trong bảng điều khiển của mình, ví dụ: khi nó bị đóng đột ngột hoặc do tắt máy ...

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

Cập nhật

Đối với những người không kiểm tra các nhận xét, có vẻ như giải pháp cụ thể này không hoạt động tốt (hoặc hoàn toàn) trên Windows 7 . Chủ đề sau nói về điều này


4
Bạn có thể sử dụng điều này để hủy bỏ lối ra không? Khác với khi nó tắt!
ingh.am

7
Điều này hoạt động tốt, chỉ bool Handler()phải return false;(nó không trả về gì trong mã) để nó hoạt động. Nếu nó trả về true, cửa sổ sẽ nhắc hộp thoại "Chấm dứt quá trình ngay bây giờ". = D
Cipi

3
Có vẻ như giải pháp này không làm việc với Windows 7 cho sự kiện tắt, hãy xem social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/...
CharlesB

3
Lưu ý rằng nếu bạn đặt một breakpoint trong phương thức 'Handler', nó sẽ ném ra một NullReferenceException. Đã kiểm tra trong VS2010, Windows 7.
Maxim

10
Điều này rất hiệu quả đối với tôi trên Windows 7 (64-bit). Không chắc tại sao mọi người nói nó không. Các sửa đổi lớn duy nhất mà tôi đã thực hiện là loại bỏ câu lệnh enum và switch, đồng thời "trả về false" khỏi phương thức - tôi thực hiện tất cả thao tác dọn dẹp của mình trong phần thân của phương thức.
BrainSlugs83

25

Ví dụ hoạt động đầy đủ, hoạt động với ctrl-c, đóng cửa sổ bằng X và giết:

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
Tôi đã thử nghiệm điều này trên windows 7 với mọi thứ được nhận xét Handlerngoại trừ return truevòng lặp và một thời gian để đếm giây. Ứng dụng này tiếp tục chạy trên ctrl-c nhưng đóng cửa sau 5 giây khi đóng cửa với X.
Antonios Hadjigeorgalis

Tôi xin lỗi nhưng bằng cách sử dụng mã này, tôi chỉ có thể nhận được thông báo "Dọn dẹp hoàn tất" nếu tôi nhấn Ctrl + C, không phải nếu tôi đóng bằng nút 'X'; trong trường hợp thứ hai, tôi chỉ nhận được "Thoát khỏi hệ thống do CTRL-C bên ngoài, hoặc quá trình giết hoặc tắt máy" nhưng sau đó có vẻ như bảng điều khiển đóng trước khi thực hiện phần còn lại của Handlerphương pháp {using Win10, .NET Framework 4.6.1}
Giacomo Pirinoli

8

Kiểm tra thêm:

AppDomain.CurrentDomain.ProcessExit

7
Điều này chỉ xuất hiện để bắt các lần thoát khỏi return hoặc Environment. Exit, nó không bắt CTRL + C, CTRL + Break, cũng như nút đóng thực tế trên bảng điều khiển.
Kit10

Nếu bạn xử lý CTRL + C riêng biệt bằng cách sử dụng Console.CancelKeyPressthì ProcessExitsự kiện thực sự được nâng lên sau khi CancelKeyPressthực thi tất cả các trình xử lý sự kiện.
Konard

5

Tôi đã gặp sự cố tương tự, chỉ là Ứng dụng bảng điều khiển của tôi sẽ chạy trong vòng lặp vô hạn với một câu lệnh ưu tiên ở giữa. Đây là giải pháp của tôi:

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

Có vẻ như bạn có các chủ đề trực tiếp kết thúc ứng dụng? Có lẽ sẽ tốt hơn nếu có một luồng báo hiệu luồng chính để nói rằng ứng dụng nên được kết thúc.

Khi nhận được tín hiệu này, luồng chính có thể tắt hoàn toàn các luồng khác và cuối cùng tự đóng lại.


3
Tôi phải đồng ý với câu trả lời này. Buộc thoát ứng dụng rồi cố gắng dọn dẹp sau đó không phải là cách anh ấy nên làm. Kiểm soát ứng dụng của bạn, Noit. Đừng để nó kiểm soát bạn.
Randolpho

1
Một luồng do tôi trực tiếp tạo ra không nhất thiết là thứ duy nhất có thể đóng ứng dụng của tôi. Ctrl-C và "nút đóng" là những cách khác mà nó có thể kết thúc. Đoạn mã được đăng bởi Frank, sau những sửa đổi nhỏ, hoàn toàn phù hợp.
ZeroKelvin

4

Câu trả lời của ZeroKelvin hoạt động trong ứng dụng bảng điều khiển Windows 10 x64, .NET 4.6. Đối với những người không cần phải xử lý enum CtrlType, đây là một cách thực sự đơn giản để kết nối với quá trình tắt của khung:

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

Trả về FALSE từ trình xử lý cho khuôn khổ biết rằng chúng ta không "xử lý" tín hiệu điều khiển và hàm xử lý tiếp theo trong danh sách trình xử lý cho quá trình này được sử dụng. Nếu không có trình xử lý nào trả về TRUE, thì trình xử lý mặc định được gọi.

Lưu ý rằng khi người dùng thực hiện đăng xuất hoặc tắt máy, lệnh gọi lại không được Windows gọi mà thay vào đó sẽ bị chấm dứt ngay lập tức.


3

Có các ứng dụng WinForms;

Application.ApplicationExit += CleanupBeforeExit;

Đối với các ứng dụng Bảng điều khiển, hãy thử

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Nhưng tôi không chắc tại thời điểm nào mà nó được gọi hoặc liệu nó có hoạt động từ bên trong miền hiện tại hay không. Tôi nghi ngờ là không.


Tài liệu trợ giúp cho DomainUnload cho biết "Người đại diện EventHandler cho sự kiện này có thể thực hiện bất kỳ hoạt động chấm dứt nào trước khi miền ứng dụng được dỡ bỏ." Vì vậy, có vẻ như nó hoạt động trong miền hiện tại. Tuy nhiên, nó có thể không phù hợp với nhu cầu của anh ấy vì các chủ đề của anh ấy có thể giữ cho miền duy trì.
Rob Parker

2
Điều này chỉ xử lý CTRL + C và CTRL + Đóng, nó không tồn tại thông qua quay lại, Môi trường.Exit cũng như nhấp vào nút đóng.
Kit10

Không bắt được CTRL + C cho tôi với Mono trên Linux.
starbeamrainbowlabs 27/12/18

2

Visual Studio 2015 + Windows 10

  • Cho phép dọn dẹp
  • Ứng dụng đơn lẻ
  • Một số mạ vàng

Mã:

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

    }
}

Điều thú vị là đây dường như là câu trả lời chắc chắn nhất. Tuy nhiên, hãy cẩn thận về việc thay đổi kích thước bộ đệm bảng điều khiển của bạn: nếu chiều cao bộ đệm nhỏ hơn chiều cao cửa sổ, chương trình sẽ ném ra một ngoại lệ khi khởi động.
John Zabroski

1

Các liên kết nêu trên bởi Charle B trong bình luận cho flq

Sâu thẳm nói:

SetConsoleCtrlHandler sẽ không hoạt động trên windows7 nếu bạn liên kết đến user32

Một số nơi khác trong chuỗi nó được đề xuất để đóng một cửa sổ ẩn. Vì vậy, tôi tạo một winform và khi tải lên, tôi gắn vào bảng điều khiển và thực thi Main gốc. Và sau đó SetConsoleCtrlHandle hoạt động tốt (SetConsoleCtrlHandle được gọi theo đề xuất của 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();
}

Trên thực tế điều này không hoạt động. Tôi có ứng dụng WFP nhiều cửa sổ và tôi sử dụng bảng điều khiển ( AllocConsolenhư trong ví dụ của bạn) để hiển thị một số thông tin bổ sung. Vấn đề là toàn bộ ứng dụng (tất cả Windows) sẽ bị đóng nếu người dùng nhấp vào (X) trên Cửa sổ điều khiển. CácSetConsoleCtrlHandler hoạt động, nhưng ứng dụng vẫn tạm dừng trước khi bất kỳ mã nào trong trình xử lý được thực thi (tôi thấy các điểm ngắt được kích hoạt và ngay sau đó ứng dụng tạm dừng).
Mike Keskinov

Nhưng tôi đã tìm thấy giải pháp phù hợp với mình - nút đóng đơn giản TÔI ĐÃ BỎ QUA . Xem: stackoverflow.com/questions/6052992/…
Mike Keskinov

0

Dành cho những ai quan tâm đến VB.net. (Tôi đã tìm kiếm trên internet và không thể tìm thấy một thứ tương đương cho nó) Ở đây nó được dịch sang 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

Giải pháp trên không hoạt động đối với tôi vb.net 4.5 Framework ControlEventType không được giải quyết. Tôi đã có thể sử dụng ý tưởng này làm giải pháp stackoverflow.com/questions/15317082/…
glant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.