キャプチャコンソール出口C#


92

非常に多くのスレッドを含むコンソールアプリケーションがあります。特定の条件を監視し、それらが真の場合にプログラムを終了するスレッドがあります。この終了はいつでも発生する可能性があります。

他のすべてのスレッドをクリーンアップしてすべてのファイルハンドルと接続を適切に閉じることができるように、プログラムの終了時にトリガーできるイベントが必要です。.NETフレームワークにすでに組み込まれているものがあるかどうかは不明なので、自分で作成する前に質問します。

次のようなイベントがあったのかと思っていました。

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
私はこれが非常に遅いコメントであることを知っていますが、「ファイルと接続のクローズ」がクリーンアップとして実行したい唯一のことである場合は、実際にそうする必要はありません。Windowsは、終了時にプロセスに関連付けられたすべてのハンドルを既に閉じているためです。
セダットカパノグル

6
^これらのリソースが終了するプロセスによって所有されている場合のみ。たとえば、あなたが背景に隠されたCOMアプリケーション(たとえば、ワード、またはExcel)を自動化している場合、これは、絶対に必要である、とあなたは、などのアプリの終了、前にそれを殺すために確認する必要があります
BrainSlugs83

1
これは、短い探して回答ありstackoverflow.com/questions/2555292/...を
barlop

回答:


96

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);
  ...
}

更新

コメントをチェックしない人にとって、この特定のソリューションはWindows 7ではうまく機能しない(またはまったく機能しない)ようです。次のスレッドはこれについて話します


4
これを使用して出口をキャンセルできますか?シャットダウン時以外は!
ingh.am

7
これはうまく機能し、bool Handler()必要なのはreturn false;(コードで何も返さない)ためです。trueを返す場合、Windowsは「Terminate Process Now」ダイアログを表示します。= D
Cipi 2010

3
このソリューションはWindows 7のシャットダウンイベントでは機能しないようです。social.msdn.microsoft.com
Forums /

3
'Handler'メソッドにブレークポイントを設定すると、NullReferenceExceptionがスローされることに注意してください。VS2010、Windows 7でチェックイン
マキシム

10
これは、Windows 7(64ビット)ではうまく機能しました。なぜ誰もがそうではないと言っているのかわかりません。私が行った唯一の主要な変更は、enumとswitchステートメントを削除することと、メソッドから「falseを返す」ことでした-メソッドの本体ですべてのクリーンアップを行います。
BrainSlugs83 2013年

25

完全に機能する例は、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..");
        }
    }
}

2
私はWindows 7でこれをテストしましたがHandlerreturn true秒を数えるためのwhileループとwhileループを除いて、すべてコメントアウトされています。Xで閉じるときにアプリケーションが5秒後には、Ctrl-Cが、閉じ上で実行し続け
アントニオHadjigeorgalis

申し訳ありませんが、このコードを使用すると、「クリーンアップ完了」を取得できます。「X」ボタンで閉じた場合ではなく、Ctrl + Cを押した場合のみです。後者の場合、「外部のCTRL-Cによるシステムの終了、またはプロセスのHandler強制終了、またはシャットダウン」しか表示されませんが、{Win10、.NET Framework 4.6.1を使用して}メソッドの残りの部分を実行する前にコンソールが閉じているようです
ジャコモ・ピリノーリ

8

また確認してください:

AppDomain.CurrentDomain.ProcessExit

7
これは、returnまたはEnvironment.Exitからの出口をキャッチするように見えるだけです。CTRL+ C、CTRL + Break、およびコンソールの実際の閉じるボタンはキャッチしません。
キット10 2012

あなたが使用して個別にCTRL + Cを処理した場合Console.CancelKeyPress、その後ProcessExitのイベントは、実際には、すべての後に発生CancelKeyPressするイベントハンドラの実行。
コナード

5

私にも同様の問題がありました。コンソールアプリが無限ループで実行され、途中で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);
    }
}

4

アプリケーションを直接終了させるスレッドがあるようですね?おそらく、スレッドをメインスレッドに通知して、アプリケーションを終了する必要があることを知らせる方が良いでしょう。

このシグナルを受信すると、メインスレッドは他のスレッドを正常にシャットダウンし、最終的に自分自身を閉じることができます。


3
私はこの答えに同意しなければなりません。アプリケーションを強制終了し、後でクリーンアップを行うのは、彼のやり方ではありません。Noit、アプリケーションを制御します。それをあなたにコントロールさせてはいけません。
ランドルフォ

1
私が直接生成したスレッドは、必ずしもアプリケーションを閉じることができる唯一のものではありません。Ctrl-Cと「閉じるボタン」は、終了できる別の方法です。フランクが投稿したコードは、わずかな変更を加えた後、完全に適合しています。
ZeroKelvin 2009年

4

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によって呼び出されず、すぐに終了することに注意してください。


3

WinFormsアプリ用です。

Application.ApplicationExit += CleanupBeforeExit;

コンソールアプリの場合は、

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

しかし、どの時点で呼び出されるのか、または現在のドメイン内から機能するかどうかはわかりません。私はそうは思わない。


DomainUnloadのヘルプドキュメントには、「このイベントのEventHandlerデリゲートは、アプリケーションドメインがアンロードされる前に終了アクティビティを実行できる」と書かれています。したがって、現在のドメイン内では機能するようです。ただし、彼のスレッドがドメインを稼働させ続ける可能性があるため、彼の必要性に対しては機能しない場合があります。
Rob Parker

2
これはCTRL + CとCTRL + Closeのみを処理し、Environment.Exitを返すことや、閉じるボタンをクリックすることで存在をキャッチしません。
キット10 2012

LinuxのMonoではCTRL + Cをキャッチできません。
starbeamrainbowlabs 2018

2

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();         
        }

    }
}

これが最も強力な答えであると思われるのは興味深いことです。ただし、コンソールのバッファーサイズの変更には注意してください。バッファーの高さがウィンドウの高さよりも小さい場合、プログラムは起動時に例外をスローします。
John Zabroski、

1

flqへのコメントのチャールBによる上記のリンク

ディープダウンと言う:

SetConsoleCtrlHandlerは、user32にリンクした場合、windows7では機能しません

スレッドの他の場所では、隠しウィンドウを作成することをお勧めします。そこで、winformを作成し、onloadでコンソールに接続して、オリジナルのMainを実行します。そして、SetConsoleCtrlHandleは正常に動作します(SetConsoleCtrlHandleは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();
}

これは実際には機能しません。マルチウィンドウのWFPアプリがあり、コンソールを使用して(AllocConsole例のように)追加情報を表示します。問題は、ユーザーがコンソールウィンドウの(X)をクリックすると、アプリ全体(すべてのWindows)が閉じられることです。SetConsoleCtrlHandler動作しますが、ハンドラ内の任意のコードが実行さとにかく前に、アプリの停止は、(私はブレークポイントを右クリックアプリの停止火力および参照します)。
マイク・ケスキノフ2016年

しかし、私はうまくいく解決策を見つけました-私は単純な無効化された閉じるボタンです。参照:stackoverflow.com/questions/6052992/...
マイクKeskinov

0

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

上記のソリューションは私にとって機能しませんvb.net 4.5フレームワークControlEventTypeは解決されません。私は、ソリューションとして、このアイデアを使用することができましたstackoverflow.com/questions/15317082/...
glant
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.