.NET Global exception handler dalam aplikasi konsol

198

Pertanyaan: Saya ingin mendefinisikan pengendali pengecualian global untuk pengecualian yang tidak ditangani dalam aplikasi konsol saya. Di asp.net, seseorang dapat mendefinisikan satu di global.asax, dan di windows aplikasi / layanan, seseorang dapat mendefinisikan seperti di bawah ini

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

Tetapi bagaimana saya bisa mendefinisikan handler pengecualian global untuk aplikasi konsol?
currentDomain tampaknya tidak berfungsi (.NET 2.0)?

Edit:

Argh, kesalahan bodoh.
Di VB.NET, seseorang perlu menambahkan kata kunci "AddHandler" di depan currentDomain, atau orang tidak akan melihat acara UnhandledException di IntelliSense ...
Itu karena kompiler VB.NET dan C # memperlakukan penanganan acara secara berbeda.

Stefan Steiger
sumber

Jawaban:

283

Tidak, itu cara yang benar untuk melakukannya. Ini berfungsi persis sebagaimana mestinya, sesuatu yang dapat Anda lakukan dari:

using System;

class Program {
    static void Main(string[] args) {
        System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;
        throw new Exception("Kaboom");
    }

    static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e) {
        Console.WriteLine(e.ExceptionObject.ToString());
        Console.WriteLine("Press Enter to continue");
        Console.ReadLine();
        Environment.Exit(1);
    }
}

Ingatlah bahwa Anda tidak dapat menangkap pengecualian tipe dan file yang dihasilkan oleh jitter dengan cara ini. Itu terjadi sebelum metode Main () Anda mulai berjalan. Menangkap itu memerlukan menunda jitter, memindahkan kode berisiko ke metode lain dan menerapkan atribut [MethodImpl (MethodImplOptions.NoInlining)] padanya.

Hans Passant
sumber
3
Saya menerapkan apa yang Anda usulkan di sini, tetapi saya tidak ingin keluar dari aplikasi. Saya hanya ingin mencatatnya, dan melanjutkan prosesnya (tanpa Console.ReadLine()atau gangguan aliran program lainnya. Tetapi yang saya dapatkan adalah pengecualian untuk meningkatkan lagi dan lagi, dan lagi.
3
@Shahrooz Jefri: Anda tidak bisa melanjutkan begitu mendapat pengecualian yang tidak tertangani. Tumpukan berantakan, dan ini terminal. Jika Anda memiliki server, yang dapat Anda lakukan di UnhandledExceptionTrapper adalah memulai ulang program dengan argumen baris perintah yang sama.
Stefan Steiger
6
Tentu saja! Kami tidak berbicara tentang acara Application.ThreadException di sini.
Hans Passant
4
Saya pikir kunci untuk memahami jawaban ini dan komentar sebagai balasan adalah untuk menyadari bahwa kode ini menunjukkan sekarang untuk mendeteksi pengecualian, tetapi tidak sepenuhnya "menangani" itu karena Anda mungkin dalam blok coba / tangkap yang normal di mana Anda memiliki pilihan untuk melanjutkan . Lihat jawaban saya yang lain untuk detailnya. Jika Anda "menangani" pengecualian dengan cara ini, Anda tidak memiliki opsi untuk terus berjalan ketika pengecualian terjadi di utas lainnya. Dalam pengertian itu, dapat dikatakan bahwa jawaban ini tidak sepenuhnya "menangani" pengecualian jika dengan "menangani" maksud Anda "proses dan terus berjalan".
BlueMonkMN
4
Belum lagi ada banyak teknologi untuk berjalan di utas yang berbeda. Misalnya, Perpustakaan Tugas Paralel (TPL) memiliki caranya sendiri untuk menangkap pengecualian yang tidak ditangani. Jadi, mengatakan ini tidak bekerja untuk semua situasi agak menggelikan, tidak ada satu tempat menangkap semua untuk semua dalam C #, tetapi tergantung pada teknologi yang Anda gunakan ada berbagai tempat Anda dapat menghubungkan ke.
Doug
23

Jika Anda memiliki aplikasi single-threaded, Anda dapat menggunakan try / catch sederhana di fungsi Utama, namun, ini tidak mencakup pengecualian yang mungkin dilemparkan di luar fungsi utama, pada utas lainnya, misalnya (seperti yang disebutkan dalam komentar). Kode ini menunjukkan bagaimana pengecualian dapat menyebabkan aplikasi berhenti meskipun Anda mencoba menanganinya di Main (perhatikan bagaimana program keluar dengan anggun jika Anda menekan enter dan memungkinkan aplikasi untuk keluar dengan anggun sebelum pengecualian terjadi, tetapi jika Anda membiarkannya berjalan , ini berakhir dengan sangat tidak senang):

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Anda dapat menerima pemberitahuan kapan utas lain membuat pengecualian untuk melakukan pembersihan sebelum aplikasi keluar, tetapi sejauh yang saya tahu, Anda tidak bisa, dari aplikasi konsol, memaksa aplikasi untuk terus berjalan jika Anda tidak menangani pengecualian pada utas dari mana ia dilemparkan tanpa menggunakan beberapa opsi kompatibilitas yang tidak jelas untuk membuat aplikasi berperilaku seperti itu dengan .NET 1.x. Kode ini menunjukkan bagaimana utas utama dapat diberitahu tentang pengecualian yang berasal dari utas lain, tetapi masih akan berakhir dengan sedih:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   Console.WriteLine("Notified of a thread exception... application is terminating.");
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Jadi menurut saya, cara paling bersih untuk menanganinya dalam aplikasi konsol adalah memastikan bahwa setiap utas memiliki penangan pengecualian di tingkat root:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   try
   {
      for (int i = 5; i >= 0; i--)
      {
         Console.Write("24/{0} =", i);
         Console.Out.Flush();
         Console.WriteLine("{0}", 24 / i);
         System.Threading.Thread.Sleep(1000);
         if (exiting) return;
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception on the other thread");
   }
}
BlueMonkMN
sumber
COBALAH CATCH tidak berfungsi dalam mode rilis untuk kesalahan tak terduga: /
Muflix
12

Anda juga perlu menangani pengecualian dari utas:

static void Main(string[] args) {
Application.ThreadException += MYThreadHandler;
}

private void MYThreadHandler(object sender, Threading.ThreadExceptionEventArgs e)
{
    Console.WriteLine(e.Exception.StackTrace);
}

Whoop, maaf itu untuk winforms, untuk setiap utas yang Anda gunakan di aplikasi konsol Anda harus melampirkan di blok try / catch. Utas latar belakang yang menemukan pengecualian tidak ditangani tidak menyebabkan aplikasi berakhir.

Es hitam
sumber
1

Saya baru saja mewarisi aplikasi konsol VB.NET tua dan perlu menyiapkan Global Exception Handler. Karena pertanyaan ini menyebutkan VB.NET beberapa kali dan ditandai dengan VB.NET, tetapi semua jawaban lain di sini adalah dalam C #, saya pikir saya akan menambahkan sintaks yang tepat untuk aplikasi VB.NET juga.

Public Sub Main()
    REM Set up Global Unhandled Exception Handler.
    AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf MyUnhandledExceptionEvent

    REM Do other stuff
End Sub

Public Sub MyUnhandledExceptionEvent(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
    REM Log Exception here and do whatever else is needed
End Sub

Saya menggunakan REMpenanda komentar daripada kutipan tunggal di sini karena Stack Overflow sepertinya menangani sintaks yang lebih baik dengan REM.

JustSomeDude
sumber
-13

Apa yang Anda coba harus bekerja sesuai dengan dokumen MSDN untuk .Net 2.0. Anda juga dapat mencoba coba / tangkap langsung di sekitar titik masuk Anda untuk aplikasi konsol.

static void Main(string[] args)
{
    try
    {
        // Start Working
    }
    catch (Exception ex)
    {
        // Output/Log Exception
    }
    finally
    {
        // Clean Up If Needed
    }
}

Dan sekarang tangkapan Anda akan menangani apa pun yang tidak tertangkap ( di utas utama ). Ini bisa menjadi anggun dan bahkan memulai kembali di tempat itu jika Anda mau, atau Anda bisa membiarkan aplikasi mati dan mencatat pengecualian. Anda akan menambahkan akhirnya jika Anda ingin melakukan pembersihan. Setiap utas akan membutuhkan penanganan tingkat tinggi sendiri yang mirip dengan utas.

Diedit untuk memperjelas poin tentang utas seperti yang ditunjukkan oleh BlueMonkMN dan ditunjukkan secara rinci dalam jawabannya.

Rodney S. Foley
sumber
1
Sayangnya, pengecualian masih bisa dilempar ke luar blok Main (). Ini sebenarnya bukan "tangkap semua" seperti yang mungkin Anda pikirkan. Lihat jawaban @Hans.
Mike Atlas
@ Mike Pertama saya mengatakan cara dia melakukannya sudah benar, dan bahwa dia bisa mencoba coba / tangkap di main. Saya tidak yakin mengapa Anda (atau orang lain) memberi saya suara ketika saya setuju dengan Hans hanya memberikan jawaban lain yang saya tidak harapkan untuk mendapatkan cek. Itu tidak benar-benar adil, dan kemudian mengatakan alternatifnya salah tanpa memberikan bukti bagaimana pengecualian yang dapat ditangkap oleh proses AppDomain UnhandledException yang tidak dapat ditangkap oleh percobaan / tangkapan di Main. Saya merasa tidak sopan mengatakan sesuatu itu salah tanpa membuktikan mengapa itu salah, hanya mengatakan itu memang benar, tidak membuatnya begitu.
Rodney S. Foley
5
Saya telah memposting contoh yang Anda minta. Harap bertanggung jawab dan hapus suara tidak relevan Anda dari jawaban lama Mike jika Anda belum. (Tidak ada kepentingan pribadi, hanya tidak suka melihat penyalahgunaan sistem seperti itu.)
BlueMonkMN
3
Namun Anda masih memainkan "permainan" yang sama dengan yang ia lakukan, hanya dengan cara yang lebih buruk karena itu adalah pembalasan murni, bukan berdasarkan kualitas jawaban. Itu bukan cara untuk memecahkan masalah, hanya memperburuknya. Ini sangat buruk ketika Anda membalas terhadap seseorang yang bahkan memiliki kekhawatiran sah tentang jawaban Anda (seperti yang telah saya tunjukkan).
BlueMonkMN
3
Oh, saya juga akan menambahkan bahwa down-voting tidak dimaksudkan untuk orang-orang yang "menjadi idiot total atau melanggar aturan", tetapi lebih untuk menilai kualitas jawaban. Sepertinya bagi saya bahwa jawaban dengan suara rendah untuk "mengomentari" orang yang memberikannya merupakan penyalahgunaan yang jauh lebih besar daripada jawaban dengan suara rendah berdasarkan pada isi dari jawaban itu sendiri terlepas dari apakah suara itu akurat atau tidak. Jangan menganggapnya terlalu pribadi.
BlueMonkMN