Konsol çıkışı C # yakala


93

Oldukça fazla iş parçacığı içeren bir konsol uygulamam var. Belirli koşulları izleyen ve doğruysa programı sonlandıran iş parçacıkları vardır. Bu fesih herhangi bir zamanda olabilir.

Program kapandığında tetiklenebilecek bir olaya ihtiyacım var, böylece diğer tüm iş parçacıklarını temizleyebilir ve tüm dosya tanıtıcılarını ve bağlantılarını düzgün bir şekilde kapatabilirim. NET çerçevesinde zaten yerleşik olup olmadığından emin değilim, bu yüzden kendiminkini yazmadan önce soruyorum.

Şu satırlarda bir olay olup olmadığını merak ediyordum:

MyConsoleProgram.OnExit += CleanupBeforeExit;

2
Bunun çok geç bir yorum olduğunu biliyorum, ancak "dosyaları ve bağlantıları kapatmak" temizleme olarak yapmak istediğiniz tek şeyse, bunu gerçekten yapmanız gerekmez. Çünkü Windows, sonlandırma sırasında bir işlemle ilişkili tüm tutamaçları zaten kapatır.
Sedat Kapanoğlu

6
^ Yalnızca bu kaynaklar sonlandırılan sürece aitse. Örneğin, arka planda gizli bir COM uygulamasını (örneğin, Word veya Excel) otomatikleştiriyorsanız ve uygulamanız çıkmadan önce onu
kapatmanız

1
bunun kısa görünümlü bir cevabı var stackoverflow.com/questions/2555292/…
barlop

Yanıtlar:


97

Kodu web'de nerede bulduğumdan emin değilim, ancak şimdi eski projelerimden birinde buldum. Bu, konsolunuzda temizleme kodu yapmanızı sağlar, örneğin aniden kapandığında veya bir kapanma nedeniyle ...

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

Güncelleme

Yorumları kontrol etmeyenler için, bu özel çözümün Windows 7'de iyi çalışmadığı (veya hiç) olmadığı görülüyor . Aşağıdaki başlık bundan bahsediyor


4
Çıkışı iptal etmek için bunu kullanabilir misin? Kapanma zamanı dışında!
ingh.am

7
Bu harika çalışıyor, yalnızca bool Handler()gerekir return false;(kodda hiçbir şey döndürmez), böylece işe yarayacaktır. True döndürürse, Windows "İşlemi Şimdi Sonlandır" iletişim kutusunu açar. = D
Cipi

3
Görünüşe göre bu çözüm, kapatma olayı için Windows 7 ile çalışmıyor, bkz. Social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/…
CharlesB

3
'İşleyici' yöntemine bir kesme noktası koyarsanız, bunun bir NullReferenceException oluşturacağını unutmayın. VS2010, Windows 7'de kontrol edildi.
Maxim

10
Bu benim için Windows 7'de (64 bit) harika çalıştı. Neden herkesin söylemediğini söylediğinden emin değilim. Yaptığım tek büyük değişiklik, enum ve switch deyiminden kurtulmak ve yöntemden "false" döndürmekti - tüm temizlememi yöntemin gövdesinde yapıyorum.
BrainSlugs83

25

Tam çalışma örneği, ctrl-c ile çalışır, pencereleri X ile kapatır ve öldürür:

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
Bunu, 7 pencerelerinde Handler, return truesaniye saymak için ve bir süre döngüsü dışında yorumlanan her şeyle test ettim . Uygulama ctrl-c ile çalışmaya devam ediyor ancak X ile kapatıldığında 5 saniye sonra kapanıyor.
Antonios Hadjigeorgalis

Üzgünüm ama bu kodu kullanarak "Temizleme tamamlandı" seçeneğini yalnızca Ctrl + C tuşlarına basarsam, 'X' düğmesiyle kapatırsam değil; ikinci durumda yalnızca "Harici CTRL-C nedeniyle sistemden çıkılıyor veya işleminHandler sonlandırılması veya kapatılması" mesajı alıyorum, ancak daha sonra , {Win10, .NET Framework 4.6.1} yönteminin geri kalan kısmını çalıştırmadan önce konsol kapanıyor gibi görünüyor
Giacomo Pirinoli

Windows 10 benim için çalışıyor pencerede CTRL-C, X VE Görev Yöneticisi'ndeki işlemi sonlandır.
JJ_Coder4Hire

8

Ayrıca şunları da kontrol edin:

AppDomain.CurrentDomain.ProcessExit

7
Bu yalnızca dönüş veya Ortam Çıkışından çıkışları yakalar gibi görünür, CTRL + C, CTRL + Break veya konsoldaki gerçek kapatma düğmesini yakalamaz.
Kit10

CTRL + C'yi ayrı ayrı kullanarak Console.CancelKeyPressişlerseniz, ProcessExitolay aslında tüm CancelKeyPressolay işleyicileri çalıştırıldıktan sonra ortaya çıkar .
Konard

5

Benzer bir sorun yaşadım, sadece konsol uygulamam ortada bir önleyici ifade ile sonsuz döngüde çalışıyordu. İşte benim çözümüm:

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

Uygulamayı doğrudan sonlandıran iş parçacıkları var gibi görünüyor? Belki de uygulamanın sonlandırılması gerektiğini söylemek için ana iş parçacığına bir iş parçacığı sinyali verilmesi daha iyi olur.

Bu sinyali alır almaz ana iş parçacığı diğer iş parçacığını temiz bir şekilde kapatabilir ve sonunda kendini kapatabilir.


3
Bu cevaba katılıyorum. Uygulamadan çıkmaya zorlamak ve ardından temizlemeye çalışmak, gidecek bir yol değil. Başvurunu kontrol et, Noit. Seni kontrol etmesine izin verme.
Randolpho

1
Doğrudan benim tarafımdan oluşturulan bir ileti dizisi, başvurumu kapatabilecek tek şey olmayabilir. Ctrl-C ve "kapat düğmesi", sona erdirmenin diğer yollarıdır. Küçük değişikliklerden sonra Frank tarafından gönderilen kod mükemmel bir şekilde uyuyor.
ZeroKelvin

4

ZeroKelvin'in cevabı Windows 10 x64, .NET 4.6 konsol uygulamasında çalışıyor. CtrlType numaralandırmasıyla uğraşması gerekmeyenler için, çerçevenin kapanmasına bağlanmanın gerçekten basit bir yolu:

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

İşleyiciden FALSE döndürmek, çerçeveye kontrol sinyalini "işlemediğimizi" ve bu işlem için işleyiciler listesindeki bir sonraki işleyici işlevinin kullanıldığını söyler. İşleyicilerin hiçbiri TRUE değerini döndürmezse, varsayılan işleyici çağrılır.

Kullanıcı oturumu kapattığında veya kapattığında, geri aramanın Windows tarafından çağrılmadığını, bunun yerine hemen sonlandırıldığını unutmayın.


3

WinForms uygulamaları için var;

Application.ApplicationExit += CleanupBeforeExit;

Konsol uygulamaları için şunu deneyin:

AppDomain.CurrentDomain.DomainUnload += CleanupBeforeExit;

Ancak bunun hangi noktada çağrıldığından veya mevcut etki alanı içinde çalışıp çalışmayacağından emin değilim. Sanmıyorum.


DomainUnload için yardım belgelerinde "Bu olay için EventHandler temsilcisi, uygulama etki alanı kaldırılmadan önce herhangi bir sonlandırma etkinliğini gerçekleştirebilir" diyor. Yani mevcut etki alanında çalışıyor gibi görünüyor. Ancak, iş parçacıkları alanı yukarıda tutabileceğinden, ihtiyacı için işe yaramayabilir.
Rob Parker

2
Bu yalnızca CTRL + C ve CTRL + Close işlemlerini gerçekleştirir, dönen, Environment.Exit veya kapat düğmesini tıklayarak varoluşu yakalamaz.
Kit10

Linux'ta Mono ile CTRL + C'yi yakalamıyor.
starbeamrainbowlabs

2

Visual Studio 2015 + Windows 10

  • Temizlemeye izin ver
  • Tek örnek uygulama
  • Biraz altın kaplama

Kod:

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

    }
}

İlginç bir şekilde bunun en sağlam cevap olduğu görülüyor. Bununla birlikte, konsol arabellek boyutunu değiştirirken dikkatli olun: arabellek yüksekliği pencere yüksekliğinden azsa, program başlangıçta bir istisna atar.
John Zabroski

1

Bağlantı FLQ için yorumda Charle B tarafından yukarıda belirtilen

Derinlerde diyor ki:

User32'ye bağlanırsanız SetConsoleCtrlHandler Windows7'de çalışmaz

İş parçacığının başka bir yerinde gizli bir pencerenin sandıklanması önerilir. Bu yüzden bir winform oluşturdum ve onload sırasında konsola bağladım ve orijinal Main'i çalıştırdım. Ve sonra SetConsoleCtrlHandle iyi çalışıyor (Flq tarafından önerildiği gibi SetConsoleCtrlHandle çağrılır)

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

Aslında bu çalışmıyor. Çok pencereli WFP uygulamam var ve AllocConsolebazı ek bilgileri göstermek için konsol ( örneğinizde olduğu gibi) kullanıyorum. Sorun, kullanıcı Konsol Penceresindeki (X) işaretine tıklarsa tüm uygulamanın (tüm Windows) kapanmasıdır. SetConsoleCtrlHandlerÇalışır, ancak işleyicisi herhangi kod çalıştırıldığında zaten önce uygulama durur (I kırılma noktaları sağa sonra uygulama durur ateş ve bakın).
Mike Keskinov

Ama benim için çalışan bir çözüm buldum - basit DEVRE DIŞI kapat düğmesi. Bakınız: stackoverflow.com/questions/6052992/…
Mike Keskinov

0

VB.net ile ilgilenenler için. (İnternette arama yaptım ve bir muadili bulamadım) Burada vb.net'e çevrildi.

    <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

Yukarıdaki çözüm benim için çalışmıyor vb.net 4.5 framework ControlEventType çözülmüyor. Bu fikri çözüm stackoverflow.com/questions/15317082/…
glant
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.