Mam aplikację konsolową, która zawiera sporo wątków. Istnieją wątki, które monitorują określone warunki i kończą program, jeśli są one prawdziwe. To wypowiedzenie może nastąpić w dowolnym momencie.
Potrzebuję zdarzenia, które może zostać wywołane podczas zamykania programu, aby móc wyczyścić wszystkie inne wątki i poprawnie zamknąć wszystkie uchwyty plików i połączenia. Nie jestem pewien, czy jest już wbudowany w framework .NET, więc pytam, zanim napiszę własny.
Zastanawiałem się, czy doszło do zdarzenia podobnego do:
MyConsoleProgram.OnExit += CleanupBeforeExit;
Odpowiedzi:
Nie jestem pewien, gdzie znalazłem kod w sieci, ale znalazłem go teraz w jednym z moich starych projektów. Umożliwi to wyczyszczenie kodu w konsoli, np. Gdy zostanie nagle zamknięta lub z powodu wyłączenia ...
[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); ... }
Aktualizacja
Dla tych, którzy nie sprawdzają komentarzy, wydaje się, że to rozwiązanie nie działa dobrze (lub wcale) w systemie Windows 7 . Mówi o tym poniższy wątek
źródło
bool Handler()
musireturn false;
(nic nie zwraca w kodzie), więc zadziała. Jeśli zwróci true, system Windows wyświetli okno dialogowe „Zakończ proces teraz”. = DW pełni działający przykład, działa z ctrl-c, zamykając okna X i zabijają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 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.."); } } }
źródło
Handler
systemie Windows 7 ze wszystkimi komentarzami z wyjątkiemreturn true
pętli i while do liczenia sekund. Aplikacja nadal działa na ctrl-c, ale zamyka się po 5 sekundach podczas zamykania z X.Handler
metody {przy użyciu Win10, .NET Framework 4.6.1}Sprawdź również:
źródło
Console.CancelKeyPress
toProcessExit
zdarzenie faktycznie wywoływane poCancelKeyPress
wykonaniu wszystkich programów obsługi zdarzeń.Miałem podobny problem, tylko moja aplikacja konsolowa działałaby w nieskończonej pętli z jedną instrukcją wyprzedzającą na środku. Oto moje rozwiązanie:
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); } }
źródło
Wygląda na to, że masz wątki bezpośrednio kończące aplikację? Być może byłoby lepiej, gdyby wątek sygnalizował główny wątek, aby powiedzieć, że aplikacja powinna zostać zakończona.
Po odebraniu tego sygnału główny wątek może czysto zamknąć inne wątki i ostatecznie zamknąć się.
źródło
Odpowiedź ZeroKelvina działa w systemie Windows 10 x64, aplikacji konsoli .NET 4.6. Dla tych, którzy nie muszą zajmować się wyliczeniem CtrlType, oto naprawdę prosty sposób na podłączenie się do zamknięcia frameworka:
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); } }
Zwrócenie FALSE z procedury obsługi mówi frameworkowi, że nie "obsługujemy" sygnału sterującego i używana jest następna funkcja obsługi z listy programów obsługi dla tego procesu. Jeśli żaden z programów obsługi nie zwraca wartości TRUE, wywoływana jest domyślna procedura obsługi.
Należy zauważyć, że gdy użytkownik wylogowuje się lub zamyka, system Windows nie wywołuje wywołania zwrotnego, ale jest natychmiast przerywany.
źródło
Jest dla aplikacji WinForms;
W przypadku aplikacji konsolowych spróbuj
Ale nie jestem pewien, w którym momencie zostanie to wywołane lub czy zadziała z bieżącej domeny. Podejrzewam, że nie.
źródło
Visual Studio 2015 + Windows 10
Kod:
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(); } } }
źródło
Link wspomniano przez Charle B w komentarzu do FLQ
Głęboko mówi:
W innym miejscu w wątku sugeruje się utworzenie ukrytego okna. Więc tworzę winform iw onload podłączyłem się do konsoli i uruchomiłem oryginalny plik Main. A potem SetConsoleCtrlHandle działa dobrze (SetConsoleCtrlHandle jest wywoływana zgodnie z sugestią 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(); }
źródło
AllocConsole
jak w twoim przykładzie), aby wyświetlić dodatkowe informacje. Problem polega na tym, że cała aplikacja (wszystkie Windows) zostanie zamknięta, jeśli użytkownik kliknie (X) w oknie konsoli. TeSetConsoleCtrlHandler
prace, ale zatrzymanie aplikacji w każdym razie przed dowolny kod w programie obsługi wykonane (widzę wartości graniczne zwolniony i tuż potem zatrzymanie APP).Dla zainteresowanych VB.net. (Przeszukałem Internet i nie mogłem znaleźć odpowiednika) Tutaj jest przetłumaczony na 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
źródło