Secara global menangkap pengecualian dalam aplikasi WPF?

242

Kami memiliki aplikasi WPF di mana sebagian dari itu dapat melempar pengecualian saat runtime. Saya ingin secara global menangkap pengecualian yang tidak ditangani dan mencatatnya, tetapi jika tidak melanjutkan eksekusi program seolah-olah tidak ada yang terjadi (seperti VB On Error Resume Next).

Apakah ini mungkin dalam C #? Dan jika demikian, di mana tepatnya saya harus meletakkan kode penanganan pengecualian?

Saat ini saya tidak bisa melihat satu titik pun di mana saya bisa membungkus try/ catchsekitar dan yang akan menangkap semua pengecualian yang bisa terjadi. Dan bahkan kemudian saya akan meninggalkan apa pun yang telah dieksekusi karena hasil tangkapan. Atau apakah saya berpikir ke arah yang salah di sini?

ETA: Karena banyak orang di bawah menunjukkannya: Aplikasi ini bukan untuk mengendalikan pembangkit listrik tenaga nuklir. Jika macet itu bukan masalah besar tapi pengecualian acak yang sebagian besar terkait UI adalah gangguan dalam konteks di mana ia akan digunakan. Ada (dan mungkin masih) beberapa dari mereka dan karena menggunakan arsitektur plugin dan dapat diperpanjang oleh orang lain (juga siswa dalam kasus itu; jadi tidak ada pengembang berpengalaman yang mampu menulis kode bebas kesalahan sepenuhnya).

Adapun pengecualian yang tertangkap: Saya log mereka ke file log, termasuk jejak stack lengkap. Itulah inti dari latihan itu. Hanya untuk melawan orang-orang yang menggunakan analogi saya dengan OB VB secara harfiah.

Saya tahu bahwa membabi buta mengabaikan kelas kesalahan tertentu berbahaya dan dapat merusak aplikasi saya. Seperti yang dikatakan sebelumnya, program ini tidak penting untuk siapa pun. Tidak ada seorang pun yang waras yang berani mempertaruhkan kelangsungan hidup peradaban manusia. Ini hanyalah alat kecil untuk menguji pendekatan desain tertentu wrt. rekayasa Perangkat Lunak.

Untuk penggunaan langsung aplikasi, tidak ada banyak hal yang dapat terjadi dengan pengecualian:

  • Tidak terkecuali penanganan - dialog kesalahan dan keluar aplikasi. Eksperimen harus diulang, meskipun mungkin dengan subjek lain. Tidak ada kesalahan yang telah dicatat, yang sangat disayangkan.
  • Penanganan pengecualian umum - kesalahan jinak terperangkap, tidak ada salahnya dilakukan. Ini harus menjadi kasus umum yang dinilai dari semua kesalahan yang kami lihat selama pengembangan. Mengabaikan kesalahan semacam ini seharusnya tidak memiliki konsekuensi langsung; struktur data inti diuji cukup baik sehingga mereka akan dengan mudah bertahan hidup ini.
  • Penanganan pengecualian umum - kesalahan serius terperangkap, mungkin macet pada titik berikutnya. Ini mungkin jarang terjadi. Kami belum pernah melihatnya sejauh ini. Kesalahan tetap dicatat dan kerusakan mungkin tidak terhindarkan. Jadi ini secara konseptual mirip dengan kasus pertama. Kecuali kita memiliki jejak stack. Dan dalam sebagian besar kasus, pengguna bahkan tidak akan melihatnya.

Adapun data percobaan yang dihasilkan oleh program: Kesalahan serius paling parah hanya akan menyebabkan tidak ada data yang direkam. Perubahan kecil yang mengubah hasil percobaan sedikit tidak mungkin. Dan bahkan dalam kasus itu, jika hasilnya tampak meragukan kesalahan telah dicatat; kita masih bisa membuang titik data itu jika itu adalah outlier total.

Untuk meringkas: Ya, saya menganggap diri saya masih setidaknya sebagian waras dan saya tidak menganggap pengecualian global menangani rutin yang membuat program berjalan harus benar-benar jahat. Seperti yang dikatakan dua kali sebelumnya, keputusan seperti itu mungkin valid, tergantung pada aplikasi. Dalam kasus ini, keputusan itu diadili sebagai keputusan yang sah dan bukan omong kosong total dan total. Untuk aplikasi lain apa pun keputusan itu mungkin terlihat berbeda. Tapi tolong jangan menuduh saya atau orang lain yang bekerja di proyek itu berpotensi meledakkan dunia hanya karena kita mengabaikan kesalahan.

Catatan: Hanya ada satu pengguna untuk aplikasi itu. Ini bukan sesuatu seperti Windows atau Office yang digunakan oleh jutaan orang di mana biaya pengecualian gelembung untuk pengguna sama sekali sudah sangat berbeda di tempat pertama.

Joey
sumber
Ini tidak benar-benar berpikir - ya, Anda mungkin ingin aplikasi keluar. TETAPI, tidak baik untuk terlebih dahulu mencatat pengecualian dengan StackTrace? Jika yang Anda dapatkan dari pengguna adalah, "Aplikasi Anda macet ketika saya menekan tombol ini", Anda mungkin tidak akan pernah bisa menyelesaikan masalah karena Anda tidak akan memiliki informasi yang cukup. Tetapi jika Anda pertama kali mencatat pengecualian sebelum membatalkan aplikasi dengan lebih menyenangkan, Anda akan memiliki lebih banyak informasi secara signifikan.
Russ
Saya menguraikan poin itu sedikit dalam pertanyaan sekarang. Saya tahu risiko yang terlibat dan untuk aplikasi tertentu itu dianggap dapat diterima. Dan batalkan aplikasi untuk sesuatu yang sederhana seperti indeks di luar batas sementara UI mencoba melakukan beberapa animasi yang bagus adalah berlebihan dan tidak dibutuhkan. Ya, saya tidak tahu penyebab pastinya, tetapi kami memiliki data untuk mendukung klaim bahwa sebagian besar kasus kesalahan tidak berbahaya. Yang serius yang kami sembunyikan mungkin menyebabkan aplikasi macet tetapi itulah yang akan terjadi tanpa penanganan pengecualian global.
Joey
Catatan lain: jika Anda mencegah crash dengan pendekatan ini, kemungkinan besar pengguna akan menyukainya.
lahjaton_j
Lihat Windows Handling Unhandled exceptionions dalam WPF (Kumpulan penangan terlengkap) sampel dalam C # untuk Visual Studio 2010 . Ini memiliki 5 contoh termasuk AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException dan AppDomain.CurrentDomain.UnhandledException.
user34660
Saya ingin menambahkan bahwa VB-like code-flow On Error Resume Nexttidak dimungkinkan dalam C #. Setelah Exception(C # tidak memiliki "kesalahan") Anda tidak bisa hanya melanjutkan dengan pernyataan berikutnya: eksekusi akan berlanjut di catchblok - atau di salah satu penangan acara yang dijelaskan dalam jawaban di bawah ini.
Mike

Jawaban:

191

Gunakan Application.DispatcherUnhandledException Event. Lihat pertanyaan ini untuk ringkasan (lihat jawaban Drew Noakes ' ).

Ketahuilah bahwa masih ada pengecualian yang menghalangi kesuksesan melanjutkan aplikasi Anda, seperti setelah stack overflow, kehabisan memori, atau kehilangan konektivitas jaringan saat Anda mencoba menyimpan ke database.

David Schmitt
sumber
Terima kasih. Dan ya, saya sadar bahwa ada pengecualian yang tidak dapat saya pulihkan, tetapi sebagian besar yang bisa terjadi tidak berbahaya dalam kasus ini.
Joey
14
Jika pengecualian Anda terjadi pada utas latar belakang (misalnya, menggunakan ThreadPool.QueueUserWorkItem), ini tidak akan berfungsi.
Szymon Rozga
@siz: poin bagus, tapi itu bisa ditangani dengan blok coba normal di workitem, bukan?
David Schmitt
1
@PitiOngmongkolkul: Pawang dipanggil sebagai event dari loop utama Anda. Ketika pengendali acara kembali, aplikasi Anda berlanjut secara normal.
David Schmitt
4
Tampaknya kita perlu mengatur e.Handled = true, di mana e adalah DispatcherUnhandledExceptionEventArgs, untuk melewati penangan default yang keluar dari program. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul
55

Kode contoh menggunakan NLog yang akan menangkap pengecualian yang dilemparkan dari semua utas di AppDomain , dari utas pengirim UI dan dari fungsi async :

App.xaml.cs:

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }
Hüseyin Yağlı
sumber
10
Ini jawaban paling lengkap di sini! Pengecualian penjadwal taks disertakan. Terbaik untuk saya, kode bersih dan sederhana.
Nikola Jovic
3
Baru-baru ini saya memiliki korupsi App.config pada seorang pelanggan, dan App-nya bahkan tidak memulai karena NLog mencoba membaca dari App.config dan melemparkan pengecualian. Karena pengecualian itu berada di penginisialisasi logger statis , itu tidak ditangkap oleh UnhandledExceptionpawang. Saya harus melihat Windows Event Log Viewer untuk menemukan apa yang terjadi ...
heltonbiker
Saya merekomendasikan untuk set e.Handled = true;di UnhandledExceptionbahwa aplikasi tidak akan crash pada Pengecualian UI
Apfelkuacha
30

AppDomain. Event UncustomedException

Acara ini memberikan pemberitahuan tentang pengecualian yang tidak tertangkap. Ini memungkinkan aplikasi untuk mencatat informasi tentang pengecualian sebelum pengendali standar sistem melaporkan pengecualian kepada pengguna dan menghentikan aplikasi.

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

Jika acara UnhandledException ditangani dalam domain aplikasi default, ia dibesarkan di sana untuk pengecualian yang tidak ditangani di utas apa pun, apa pun domain aplikasi yang digunakan utas. Jika utas dimulai pada domain aplikasi yang memiliki pengendali acara untuk UnhandledException, acara dimunculkan dalam domain aplikasi itu. Jika domain aplikasi itu bukan domain aplikasi default, dan ada juga pengendali event di domain aplikasi default, acara tersebut dimunculkan di kedua domain aplikasi.

Misalnya, misalkan utas dimulai pada domain aplikasi "AD1", memanggil metode dalam domain aplikasi "AD2", dan dari sana memanggil metode dalam domain aplikasi "AD3", di mana ia mengeluarkan pengecualian. Domain aplikasi pertama tempat acara UnhandledException dapat dimunculkan adalah "AD1". Jika domain aplikasi itu bukan domain aplikasi default, acara tersebut juga dapat dimunculkan dalam domain aplikasi default.

CharithJ
sumber
menyalin dari URL yang Anda tunjuk (yang hanya berbicara tentang aplikasi konsol dan aplikasi WinForms, saya kira): "Dimulai dengan .NET Framework 4, acara ini tidak dimunculkan untuk pengecualian yang merusak kondisi proses, seperti tumpukan berlebih atau pelanggaran akses, kecuali event handler kritis terhadap keamanan dan memiliki atribut HandleProcessCorruptedStateExceptionsAttribute. "
George Birbilis
1
@GeorgeBirbilis: Anda dapat berlangganan untuk acara UnhandledException di konstruktor App.
CharithJ
18

Selain apa yang disebutkan orang lain di sini, perhatikan bahwa menggabungkan Application.DispatcherUnhandledException(dan sejenisnya ) dengan

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

di app.configakan mencegah pengecualian utas sekunder Anda mematikan aplikasi.

Hertzel Guinness
sumber
2

Berikut ini adalah contoh lengkap menggunakan NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}
Pengembang
sumber
-4

Seperti "VB's On Error Resume Next?" Kedengarannya agak menakutkan. Rekomendasi pertama adalah jangan lakukan itu. Rekomendasi kedua adalah jangan lakukan itu dan jangan pikirkan itu. Anda perlu mengisolasi kesalahan Anda dengan lebih baik. Mengenai cara mendekati masalah ini, itu tergantung pada bagaimana Anda menyusun kode. Jika Anda menggunakan pola seperti MVC atau sejenisnya maka ini seharusnya tidak terlalu sulit dan pasti tidak memerlukan penelepon pengecualian global. Kedua, cari perpustakaan logging yang bagus seperti log4net atau gunakan tracing. Kami perlu mengetahui detail lebih lanjut seperti apa pengecualian yang Anda bicarakan dan bagian mana dari aplikasi Anda yang dapat menyebabkan pengecualian dilemparkan.

BobbyShaftoe
sumber
2
Intinya adalah bahwa saya ingin menjaga aplikasi tetap berjalan bahkan setelah pengecualian tanpa tertangkap. Saya hanya ingin mencatatnya (kami memiliki kerangka penebangan khusus untuk ini, berdasarkan kebutuhan kami) dan tidak perlu membatalkan seluruh program hanya karena beberapa plugin melakukan sesuatu yang aneh dalam kode interaksi penggunanya. Dari apa yang saya lihat sejauh ini, tidak sepele untuk mengisolasi penyebabnya dengan bersih karena bagian-bagian yang berbeda tidak saling mengenal satu sama lain.
Joey
9
Saya mengerti tetapi Anda harus sangat berhati-hati untuk melanjutkan setelah pengecualian bahwa Anda tidak memeriksa, ada kemungkinan korupsi data dan sebagainya untuk dipertimbangkan.
BobbyShaftoe
3
Saya setuju dengan BobbyShaftoe. Ini bukan cara yang benar untuk pergi. Biarkan aplikasi mogok. Jika kesalahan ada pada beberapa plugin, maka perbaiki atau mintalah seseorang untuk memperbaiki plugin tersebut. Membiarkannya berjalan sangat berbahaya. Anda akan mendapatkan efek samping yang aneh dan akan mencapai titik di mana Anda tidak akan dapat menemukan penjelasan logis untuk bug yang terjadi di aplikasi Anda.
1
Saya menguraikan poin itu sedikit dalam pertanyaan sekarang. Saya tahu risiko yang terlibat dan untuk aplikasi tertentu itu dianggap dapat diterima. Dan batalkan aplikasi untuk sesuatu yang sederhana seperti indeks di luar batas sementara UI mencoba melakukan beberapa animasi yang bagus adalah berlebihan dan tidak dibutuhkan. Ya, saya tidak tahu penyebab pastinya, tetapi kami memiliki data untuk mendukung klaim bahwa sebagian besar kasus kesalahan tidak berbahaya. Yang serius yang kami sembunyikan mungkin menyebabkan aplikasi macet tetapi itulah yang akan terjadi tanpa penanganan pengecualian global.
Joey
Ini harus berupa komentar, bukan jawaban. Menanggapi "bagaimana saya melakukan ini" dengan "Anda mungkin tidak seharusnya" bukanlah jawaban, itu adalah komentar.
Josh Noe