#c# #windows #console-application #kill-process
#c# #Windows #консольное приложение #kill-process
Вопрос:
Мне нужно иметь возможность корректно завершать работу консольного приложения по требованию.
Используя этот пост, я смог контролировать выход при получении событий CTRL. Тем не менее, я не могу остановить приложение по требованию, используя taskkill .
Если я попробую выполнить команду:
taskkill /im myapp
он отвечает:
ОШИБКА: процесс «MyApp.exe » с PID 23296 не удалось завершить работу. Причина: этот процесс может быть завершен только принудительно (с параметром /F ).
Комментарии:
1.
taskkill
хочет изящно завершить работу, отправивWM_QUIT
сообщение в главное окно. К сожалению, в консольных приложениях нет окон или циклов сообщений (если только они не стараются изо всех сил создавать их, то есть), поэтому у него нет вариантов для изящного завершения — в Windows нет согласованного механизма, который сигнализировал бы консольному приложению о необходимости корректного завершения. (Я не проверял, действительно ли создание цикла сообщений / невидимого окна позволитtaskkill
завершить его изящно — вы могли бы это проверить.)2. Окно консоли имеет специальный корпус, чтобы иметь эффективного владельца для
GetWindowProcessThreadId
запроса. Это процесс, который был наименее недавно подключен к консоли, который обычно является процессом выделения. Если мы не принудительно убиваем этот ведущий процесс, он отправляетWM_CLOSE
(неWM_QUIT
) в окно консоли, а консоль, в свою очередь, отправляет событие Ctrl Close всем процессам , подключенным к нему. У них есть 5 секунд для корректного завершения, прежде чем они будут принудительно завершены.3. Если вам нужно такое поведение для любого консольного процесса, а не только для ведущего процесса, напишите небольшую программу / скрипт, который вызывает
AttachConsole(pid)
, а затем передает Ctrl C или Ctrl Break всем подключенным процессам (группе процессов 0) черезGenerateConsoleCtrlEvent
. Во-первых, для вашего собственного процесса вам нужно вызватьSetConsoleCtrlHandler
, чтобы игнорировать Ctrl C или установить обработчик, который возвращаетTRUE
forCTRL_BREAK_EVENT
, в зависимости от того, какое событие вы генерируете.4. Вы даже можете реализовать принудительное завершение для всех подключенных процессов. Вызов
GetConsoleProcessList
. Откройте дескриптор для каждого процесса (без себя) с помощью terminate access. ВызовGenerateConsoleCtrlEvent
. Подождите до 5 секунд, а затем вызовитеTerminateProcess
любой процесс, который еще не завершен.5. @JeroenMostert, по крайней мере, в Windows 10, taskkill.exe перечисляет невидимые окна только для сообщений в дополнение к видимым окнам верхнего уровня, чтобы найти все окна, связанные с процессом. IIRC в предыдущих версиях не проверял окна только для сообщений.
Ответ №1:
Основываясь на многих вкладах, добавленных в этот пост, а также на обнаружении-console-application-exit-in-c, я создаю следующий помощник, чтобы корректно останавливать консольное приложение по сигналу taskkill, а также останавливать любые другие сигналы close controls:
public class StopController : Form, IMessageFilter
{
//logger
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static private CancellationTokenSource cancellationTokenSource;
public StopController(CancellationTokenSource cancellationTokenSource)
{
StopController.cancellationTokenSource = cancellationTokenSource;
System.Windows.Forms.Application.AddMessageFilter(this);
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 16)
{
log.Warn("Receiveing WF_Close event. Cancel was fired.");
cancellationTokenSource.Cancel();
}
return true;
}
public static void Activate(CancellationTokenSource cancellationTokenSource)
{
Task.Run(() => Application.Run(new StopController(cancellationTokenSource)));
}
#region unmanaged
//must be static.
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
// Put your own handler here
switch (ctrlType)
{
case CtrlTypes.CTRL_C_EVENT:
log.Warn("CTRL C received!. Cancel was fired.");
cancellationTokenSource.Cancel();
break;
case CtrlTypes.CTRL_BREAK_EVENT:
log.Warn("CTRL BREAK received!. Cancel was fired.");
cancellationTokenSource.Cancel();
break;
case CtrlTypes.CTRL_CLOSE_EVENT:
log.Warn("Program being closed!. Cancel was fired.");
cancellationTokenSource.Cancel();
break;
case CtrlTypes.CTRL_LOGOFF_EVENT:
case CtrlTypes.CTRL_SHUTDOWN_EVENT:
log.Warn("User is logging off!. Cancel was fired.");
cancellationTokenSource.Cancel();
break;
default:
log.Warn($"unknow type {ctrlType}");
break;
}
return true;
}
// Declare the SetConsoleCtrlHandler function
// as external and receiving a delegate.
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
// An enumerated type for the control messages
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
#endregion
}
Использование:
StopController.Activate(cancellationTokenSource);
Нет необходимости переходить на приложение Windows.