Событие выхода из консольного приложения.NET
Есть ли в.NET метод, например событие, для определения момента выхода из консольного приложения? Мне нужно очистить некоторые потоки и COM-объекты.
Я запускаю цикл сообщений без формы из консольного приложения. Компонент DCOM, который я использую, кажется, требует, чтобы приложение качало сообщения.
Я попытался добавить обработчик для Process.GetCurrentProcess.Exited и Process.GetCurrentProcess.Disposed.
Я также попытался добавить обработчик к событиям Application.ApplicationExit и Application.ThreadExit, но они не запускаются. Возможно, это потому, что я не использую форму.
6 ответов
Вы можете использовать ProcessExit
событие AppDomain
:
class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
// do some work
}
static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
Console.WriteLine("exit");
}
}
Обновить
Вот полный пример программы с пустым "насосом сообщений", работающим в отдельном потоке, который позволяет пользователю ввести команду выхода в консоли, чтобы корректно закрыть приложение. После цикла в MessagePump вы, вероятно, захотите аккуратно очистить ресурсы, используемые потоком. Там лучше сделать это, чем в ProcessExit по нескольким причинам:
- Избегайте проблем с многопоточностью; если в потоке MessagePump были созданы внешние COM-объекты, с ними легче иметь дело.
- На ProcessExit существует ограничение по времени (3 секунды по умолчанию), поэтому, если очистка занимает много времени, она может завершиться неудачей, если будет обработана в этом обработчике событий.
Вот код:
class Program
{
private static bool _quitRequested = false;
private static object _syncLock = new object();
private static AutoResetEvent _waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
// start the message pumping thread
Thread msgThread = new Thread(MessagePump);
msgThread.Start();
// read input to detect "quit" command
string command = string.Empty;
do
{
command = Console.ReadLine();
} while (!command.Equals("quit", StringComparison.InvariantCultureIgnoreCase));
// signal that we want to quit
SetQuitRequested();
// wait until the message pump says it's done
_waitHandle.WaitOne();
// perform any additional cleanup, logging or whatever
}
private static void SetQuitRequested()
{
lock (_syncLock)
{
_quitRequested = true;
}
}
private static void MessagePump()
{
do
{
// act on messages
} while (!_quitRequested);
_waitHandle.Set();
}
static void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
Console.WriteLine("exit");
}
}
Вот полное, очень простое решение.Net, которое работает во всех версиях Windows. Просто вставьте его в новый проект, запустите его и попробуйте CTRL-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 biolerplate 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..");
}
}
}
Приложение представляет собой сервер, который просто запускается до тех пор, пока система не завершит работу или не получит Ctrl+C или окно консоли не будет закрыто.
Из-за необычного характера приложения "изящно" выйти невозможно. (Возможно, я мог бы написать другое приложение, которое отправило бы сообщение "завершение работы сервера", но это было бы излишним для одного приложения и все же недостаточным для определенных обстоятельств, например, когда сервер (Фактическая ОС) фактически выключается.)
Из-за этих обстоятельств я добавил ConsoleCtrlHandler, где я останавливаю свои потоки и очищаю свои COM-объекты и т. Д.
Public Declare Auto Function SetConsoleCtrlHandler Lib "kernel32.dll" (ByVal Handler As HandlerRoutine, ByVal Add As Boolean) As Boolean
Public Delegate Function HandlerRoutine(ByVal CtrlType As CtrlTypes) As Boolean
Public Enum CtrlTypes
CTRL_C_EVENT = 0
CTRL_BREAK_EVENT
CTRL_CLOSE_EVENT
CTRL_LOGOFF_EVENT = 5
CTRL_SHUTDOWN_EVENT
End Enum
Public Function ControlHandler(ByVal ctrlType As CtrlTypes) As Boolean
.
.clean up code here
.
End Function
Public Sub Main()
.
.
.
SetConsoleCtrlHandler(New HandlerRoutine(AddressOf ControlHandler), True)
.
.
End Sub
Эта установка, кажется, работает отлично. Вот ссылка на некоторый код C# для того же самого.
Для случая CTRL+C вы можете использовать это:
// Tell the system console to handle CTRL+C by calling our method that
// gracefully shuts down.
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
Console.WriteLine("Shutting down...");
// Cleanup here
System.Threading.Thread.Sleep(750);
}
Если вы используете консольное приложение и отправляете сообщения, не можете ли вы использовать сообщение WM_QUIT?
В качестве хорошего примера, возможно, стоит перейти к этому проекту и посмотреть, как обрабатывать выходящие процессы грамматически или в этом фрагменте из VM, найденном здесь.
ConsoleOutputStream = new ObservableCollection<string>();
var startInfo = new ProcessStartInfo(FilePath)
{
WorkingDirectory = RootFolderPath,
Arguments = StartingArguments,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
ConsoleProcess = new Process {StartInfo = startInfo};
ConsoleProcess.EnableRaisingEvents = true;
ConsoleProcess.OutputDataReceived += (sender, args) =>
{
App.Current.Dispatcher.Invoke((System.Action) delegate
{
ConsoleOutputStream.Insert(0, args.Data);
//ConsoleOutputStream.Add(args.Data);
});
};
ConsoleProcess.Exited += (sender, args) =>
{
InProgress = false;
};
ConsoleProcess.Start();
ConsoleProcess.BeginOutputReadLine();
}
}
private void RegisterProcessWatcher()
{
startWatch = new ManagementEventWatcher(
new WqlEventQuery($"SELECT * FROM Win32_ProcessStartTrace where ProcessName = '{FileName}'"));
startWatch.EventArrived += new EventArrivedEventHandler(startProcessWatch_EventArrived);
stopWatch = new ManagementEventWatcher(
new WqlEventQuery($"SELECT * FROM Win32_ProcessStopTrace where ProcessName = '{FileName}'"));
stopWatch.EventArrived += new EventArrivedEventHandler(stopProcessWatch_EventArrived);
}
private void stopProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
{
InProgress = false;
}
private void startProcessWatch_EventArrived(object sender, EventArrivedEventArgs e)
{
InProgress = true;
}