非常に多くのスレッドを含むコンソールアプリケーションがあります。特定の条件を監視し、それらが真である場合にプログラムを終了するスレッドがあります。この終了はいつでも発生する可能性があります。
他のすべてのスレッドをクリーンアップし、すべてのファイルハンドルと接続を適切に閉じることができるように、プログラムを閉じるときにトリガーできるイベントが必要です。 .NETフレームワークに既に組み込まれているものがあるかどうかはわかりませんので、自分で書く前に尋ねています。
私は次のような行に沿ってイベントがあったかどうか疑問に思っていました:
MyConsoleProgram.OnExit += CleanupBeforeExit;
Webのどこでコードを見つけたかはわかりませんが、今では古いプロジェクトの1つで見つけました。これにより、コンソールでクリーンアップコードを実行できます。突然閉じられたとき、またはシャットダウンのために...
[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);
...
}
更新
コメントをチェックしていない人にとっては、この特定のソリューションはnotがWindowsでうまく(またはまったく)動作しないようです7。次の thread はこれについて語っています
完全な動作例、ctrl-cで動作し、Xでウィンドウを閉じてkillします:
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..");
}
}
}
以下も確認してください:
AppDomain.CurrentDomain.ProcessExit
スレッドがアプリケーションを直接終了しているようですね。おそらく、アプリケーションを終了する必要があることを伝えるために、スレッドにメインスレッドに信号を送る方がよいでしょう。
このシグナルを受信すると、メインスレッドは他のスレッドを完全にシャットダウンし、最終的に自身を閉じることができます。
同様の問題がありました。コンソールアプリだけが無限ループで実行され、途中で1つのプリエンプティブステートメントが実行されました。私の解決策は次のとおりです。
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);
}
}
WinFormsアプリ用です。
Application.ApplicationExit += CleanupBeforeExit;
コンソールアプリの場合、試してください
AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;
しかし、どの時点で呼び出されるか、または現在のドメイン内から機能するかどうかはわかりません。私はそうは思わない。
ZeroKelvinの答えは、Windows 10 x64、.NET 4.6コンソールアプリで機能します。 CtrlType列挙型を扱う必要がない人のために、フレームワークのシャットダウンにフックする本当に簡単な方法を以下に示します。
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);
}
}
ハンドラーからFALSEを返すと、制御信号を「処理」していないことがフレームワークに通知され、このプロセスのハンドラーリスト内の次のハンドラー関数が使用されます。どのハンドラーもTRUEを返さない場合、デフォルトのハンドラーが呼び出されます。
ユーザーがログオフまたはシャットダウンを実行すると、コールバックはWindowsによって呼び出されず、代わりにすぐに終了することに注意してください。
リンク は、前述のflqへのコメントでCharle Bが言及したものです。
深く言う:
User32にリンクすると、Windows 7ではSetConsoleCtrlHandlerが機能しません。
スレッド内の他の場所では、非表示のウィンドウを作成することをお勧めします。だから私はwinformを作成し、コンソールに接続したonloadで元のMainを実行します。そして、SetConsoleCtrlHandleは正常に動作します(flqが示唆するようにSetConsoleCtrlHandleが呼び出されます)。
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();
}
Visual Studio 2015 + Windows 10
コード:
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();
}
}
}
VB.netに興味がある人向け。 (私はインターネットを検索し、それと同等のものを見つけることができませんでした)ここでは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