Как мне перехватить ctrl-c (SIGINT) в консольном приложении C #
222
Я хотел бы иметь возможность перехватывать CTRL+ Cв консольном приложении C #, чтобы я мог выполнить некоторые очистки перед выходом. Каков наилучший способ сделать это?
На самом деле, эта статья рекомендует P / Invoke и CancelKeyPressупоминается лишь кратко в комментариях. Хорошая статья - codeneverwritten.com/2006/10/…
bzlm
Это работает, но не закрывает окно с помощью X. Смотрите мое полное решение ниже. работает и с kill
JJ_Coder4Hire
1
Я обнаружил, что Console.CancelKeyPress перестанет работать, если консоль закрыта. Запуск приложения под mono / linux с помощью systemd или если приложение запускается как «mono myapp.exe </ dev / null», SIGINT будет отправлен обработчику сигналов по умолчанию и сразу же убьет приложение. Пользователи Linux могут захотеть увидеть stackoverflow.com/questions/6546509/…
@aku, неужели у тебя нет времени, чтобы ответить на SIGINT, прежде чем процесс будет прерван? Я нигде не могу его найти, и я пытаюсь вспомнить, где я это читал.
publicstaticvoidMain(string[] args){Console.CancelKeyPress+=delegate{// call methods to clean up};while(true){}}
Когда пользователь нажимает Ctrl + C, код в делегате запускается и программа завершается. Это позволяет выполнять очистку, вызывая необходимые методы. Обратите внимание, что после делегата код не выполняется.
Есть другие ситуации, когда это не поможет. Например, если в настоящее время программа выполняет важные вычисления, которые нельзя немедленно остановить. В этом случае правильной стратегией может быть указание программе выйти после завершения расчета. Следующий код дает пример того, как это можно реализовать:
classMainClass{privatestaticbool keepRunning =true;publicstaticvoidMain(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");}}
Разница между этим кодом и первым примером состоит в том, что e.Cancelустановлено значение true, что означает, что выполнение продолжается после делегата. При запуске программа ожидает, когда пользователь нажмет Ctrl + C. Когда это произойдет, keepRunningпеременная изменит значение, что приведет к выходу из цикла while. Это способ изящно завершить работу программы.
keepRunningвозможно, потребуется пометить volatile. В противном случае основной поток может кэшировать его в регистре ЦП и не заметит изменения значения при выполнении делегата.
cdhowie
Это работает, но не закрывает окно с помощью X. Смотрите мое полное решение ниже. также работает с kill.
JJ_Coder4Hire
13
Следует изменить, чтобы использовать ManualResetEventвместо вращения bool.
Мизипзор
Небольшое предостережение для всех, кто запускает запущенные программы в Git-Bash, MSYS2 или CygWin: вам нужно будет запустить dotnet через winpty, чтобы это работало (Итак, winpty dotnet run). В противном случае делегат никогда не будет запущен.
Я хотел бы добавить к ответу Джонаса . Вращение на boolбудет вызывать 100% загрузку процессора и тратить кучу энергии, ничего не делая, ожидая CTRL+ C.
Лучшее решение - использовать « ManualResetEventна самом деле» для ожидания CTRL+ C:
staticvoidMain(string[] args){var exitEvent =newManualResetEvent(false);Console.CancelKeyPress+=(sender, eventArgs)=>{
eventArgs.Cancel=true;
exitEvent.Set();};var server =newMyServer();// example
server.Run();
exitEvent.WaitOne();
server.Stop();}
Я думаю, дело в том, что вы будете выполнять всю работу внутри цикла while, и нажатие Ctrl + C не будет прерываться в середине итерации while; это завершит эту итерацию, прежде чем вырваться.
pkr298
2
@ pkr298 - Жаль, что люди не голосуют за ваш комментарий, потому что он полностью правдив. Я отредактирую его ответ Джонаса, чтобы прояснить, как люди думают так, как поступил Джонатон (что не так уж плохо, как Джонас имел в виду его ответ)
М. Мимпен
Обновите существующий ответ этим улучшением.
Мизипзор
27
Вот полный рабочий пример. вставьте в пустой консольный проект C #:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace TestTrapCtrlC{publicclassProgram{staticbool exitSystem =false;#region Trap application termination[DllImport("Kernel32")]privatestaticexternboolSetConsoleCtrlHandler(EventHandler handler,booladd);privatedelegateboolEventHandler(CtrlType sig);staticEventHandler _handler;enumCtrlType{
CTRL_C_EVENT =0,
CTRL_BREAK_EVENT =1,
CTRL_CLOSE_EVENT =2,
CTRL_LOGOFF_EVENT =5,
CTRL_SHUTDOWN_EVENT =6}privatestaticboolHandler(CtrlType sig){Console.WriteLine("Exiting system due to external CTRL-C, or process kill, or shutdown");//do your cleanup hereThread.Sleep(5000);//simulate some cleanup delayConsole.WriteLine("Cleanup complete");//allow main to run off
exitSystem =true;//shutdown right away so there are no lingering threadsEnvironment.Exit(-1);returntrue;}#endregionstaticvoidMain(string[] args){// Some biolerplate to react to close window event, CTRL-C, kill, etc
_handler +=newEventHandler(Handler);SetConsoleCtrlHandler(_handler,true);//start your multi threaded program hereProgram p =newProgram();
p.Start();//hold the console so it doesn’t run off the endwhile(!exitSystem){Thread.Sleep(500);}}publicvoidStart(){// start a thread and start doing some processingConsole.WriteLine("Thread started, processing..");}}}
Я просто добавил это, потому что это единственный ответ, который касается полного ответа. Это полный и позволяет запускать программу в планировщике задач без участия пользователя. У тебя все еще есть шанс на уборку. Используйте NLOG в своем проекте, и у вас есть что-то управляемое. Интересно, скомпилируется ли он в .NET Core 2 или 3.
Вот как я решил эту проблему, и имел дело с пользователем, нажимающим X, а также Ctrl-C. Обратите внимание на использование ManualResetEvents. Это приведет к тому, что основной поток перейдет в спящий режим, что освободит ЦП для обработки других потоков в ожидании выхода или очистки. ПРИМЕЧАНИЕ. Необходимо установить TerminationCompletedEvent в конце main. Невыполнение этого требования приводит к ненужной задержке при завершении из-за истечения времени ожидания ОС при уничтожении приложения.
namespace CancelSample{
using System;
using System.Threading;
using System.Runtime.InteropServices;internalclassProgram{/// <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")]privatestaticexternboolSetConsoleCtrlHandler(ConsoleCloseHandler handler,booladd);/// <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>privatedelegateboolConsoleCloseHandler(int closeReason);/// <summary>/// Event set when the process is terminated./// </summary>privatestaticreadonlyManualResetEventTerminationRequestedEvent;/// <summary>/// Event set when the process terminates./// </summary>privatestaticreadonlyManualResetEventTerminationCompletedEvent;/// <summary>/// Static constructor/// </summary>staticProgram(){// Do this initialization here to avoid polluting Main() with it// also this is a great place to initialize multiple static// variables.TerminationRequestedEvent=newManualResetEvent(false);TerminationCompletedEvent=newManualResetEvent(false);SetConsoleCtrlHandler(OnConsoleCloseEvent,true);}/// <summary>/// The main console entry point./// </summary>/// <param name="args">The commandline arguments.</param>privatestaticvoidMain(string[] args){// Wait for the termination eventwhile(!TerminationRequestedEvent.WaitOne(0)){// Something to do while waitingConsole.WriteLine("Work");}// Sleep until terminationTerminationRequestedEvent.WaitOne();// Print a message which represents the operationConsole.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>privatestaticboolOnConsoleCloseEvent(int reason){// Signal terminationTerminationRequestedEvent.Set();// Wait for cleanupTerminationCompletedEvent.WaitOne();// Don't run other handlers, just exit.returntrue;}}}
Это может привести к тому, что ReadLine потребует два нажатия Enter для каждого входа.
Grault
0
Я могу провести некоторые очистки перед выходом. Каков наилучший способ сделать это? Вот настоящая цель: выйти из ловушки, чтобы сделать свои собственные вещи. И более четкие ответы выше не делают это правильно. Потому что Ctrl + C - это только один из многих способов выхода из приложения.
Что для этого нужно в dotnet c # - так называемый токен отмены, который передается, Host.RunAsync(ct)а затем, в ловушках сигналов выхода, для Windows это будет
CancelKeyPress
упоминается лишь кратко в комментариях. Хорошая статья - codeneverwritten.com/2006/10/…Для этого используется событие Console.CancelKeyPress . Вот как это используется:
Когда пользователь нажимает Ctrl + C, код в делегате запускается и программа завершается. Это позволяет выполнять очистку, вызывая необходимые методы. Обратите внимание, что после делегата код не выполняется.
Есть другие ситуации, когда это не поможет. Например, если в настоящее время программа выполняет важные вычисления, которые нельзя немедленно остановить. В этом случае правильной стратегией может быть указание программе выйти после завершения расчета. Следующий код дает пример того, как это можно реализовать:
Разница между этим кодом и первым примером состоит в том, что
e.Cancel
установлено значение true, что означает, что выполнение продолжается после делегата. При запуске программа ожидает, когда пользователь нажмет Ctrl + C. Когда это произойдет,keepRunning
переменная изменит значение, что приведет к выходу из цикла while. Это способ изящно завершить работу программы.источник
keepRunning
возможно, потребуется пометитьvolatile
. В противном случае основной поток может кэшировать его в регистре ЦП и не заметит изменения значения при выполнении делегата.ManualResetEvent
вместо вращенияbool
.winpty dotnet run
). В противном случае делегат никогда не будет запущен.Я хотел бы добавить к ответу Джонаса . Вращение на
bool
будет вызывать 100% загрузку процессора и тратить кучу энергии, ничего не делая, ожидая CTRL+ C.Лучшее решение - использовать «
ManualResetEvent
на самом деле» для ожидания CTRL+ C:источник
Вот полный рабочий пример. вставьте в пустой консольный проект C #:
источник
Этот вопрос очень похож на:
Захватить консоль выхода C #
Вот как я решил эту проблему, и имел дело с пользователем, нажимающим X, а также Ctrl-C. Обратите внимание на использование ManualResetEvents. Это приведет к тому, что основной поток перейдет в спящий режим, что освободит ЦП для обработки других потоков в ожидании выхода или очистки. ПРИМЕЧАНИЕ. Необходимо установить TerminationCompletedEvent в конце main. Невыполнение этого требования приводит к ненужной задержке при завершении из-за истечения времени ожидания ОС при уничтожении приложения.
источник
Console.TreatControlCAsInput = true;
работал для меня.источник
Что для этого нужно в dotnet c # - так называемый токен отмены, который передается,
Host.RunAsync(ct)
а затем, в ловушках сигналов выхода, для Windows это будет...
источник