C # menangkap pengecualian stack overflow

115

Saya memiliki panggilan rekursif ke metode yang melempar pengecualian stack overflow. Panggilan pertama dikelilingi oleh blok coba tangkap tetapi pengecualian tidak tertangkap.

Apakah pengecualian stack overflow berperilaku dengan cara khusus? Bisakah saya menangkap / menangani pengecualian dengan benar?

Tidak yakin apakah relevan, tetapi informasi tambahan:

  • pengecualian tidak ditampilkan di utas utama

  • objek tempat kode melemparkan pengecualian dimuat secara manual oleh Assembly.LoadFrom (...). CreateInstance (...)

Toto
sumber
3
@RichardOD, tentu saya memperbaiki bug karena itu adalah bug. Namun masalah ini dapat muncul dengan cara yang berbeda dan saya ingin menanganinya
Toto
7
Setuju, stack overflow adalah kesalahan serius yang tidak dapat ditangkap karena tidak boleh tertangkap. Perbaiki kode yang rusak.
Ian Kemp
11
@RichardOD: Jika seseorang ingin merancang, misalnya pengurai keturunan rekursif dan tidak memaksakan batasan buatan pada kedalaman di luar yang sebenarnya dibutuhkan oleh mesin host, bagaimana cara melakukannya? Jika saya memiliki druthers saya, akan ada pengecualian StackCritical yang dapat ditangkap secara eksplisit, yang akan ditembakkan saat masih ada sedikit ruang tumpukan yang tersisa; itu akan menonaktifkan dirinya sendiri sampai benar-benar terlempar, dan kemudian tidak dapat ditangkap sampai jumlah ruang tumpukan yang aman tersisa.
supercat
2
Pertanyaan ini berguna - Saya ingin gagal dalam pengujian unit jika terjadi pengecualian stack overflow - tetapi NUnit hanya memindahkan pengujian ke kategori "diabaikan" alih-alih gagal seperti halnya dengan pengecualian lain - Saya perlu menangkapnya dan lakukan sebagai Assert.Failgantinya. Sangat serius - bagaimana kita melakukannya?
BrainSlugs83

Jawaban:

109

Dimulai dengan 2.0, Pengecualian StackOverflow hanya dapat ditangkap dalam keadaan berikut.

  1. CLR dijalankan dalam lingkungan yang dihosting * di mana host secara khusus mengizinkan pengecualian StackOverflow untuk ditangani
  2. Pengecualian stackoverflow dilemparkan oleh kode pengguna dan bukan karena situasi stack overflow yang sebenarnya ( Referensi )

* "lingkungan yang dihosting" seperti dalam "kode saya menghosting CLR dan saya mengonfigurasi opsi CLR" dan bukan "kode saya berjalan di hosting bersama"

JaredPar
sumber
27
Jika tidak dapat ditangkap dalam scebario yang relevan, mengapa objek StackoverflowException ada?
Manu
9
@Manu setidaknya karena beberapa alasan. 1) Apakah itu bisa ditangkap, jenis, di 1.1 dan karenanya punya tujuan. 2) Itu masih bisa ditangkap jika Anda menghosting CLR jadi itu masih merupakan jenis pengecualian yang valid
JaredPar
3
Jika tidak dapat ditangkap ... Mengapa peristiwa windows tidak menjelaskan apa yang terjadi menyertakan pelacakan tumpukan penuh secara default?
10
Bagaimana cara mengizinkan StackOverflowExceptions ditangani dalam lingkungan yang dihosting? Alasan saya bertanya adalah karena saya menjalankan lingkungan yang dihosting, dan saya mengalami masalah yang sama persis, yang menghancurkan seluruh kumpulan aplikasi. Saya lebih suka jika itu membatalkan utas, di mana ia dapat melepas kembali ke atas, dan saya kemudian dapat mencatat kesalahan dan melanjutkan tanpa semua utas apppool terbunuh.
Brain2000
Starting with 2.0 ..., Saya penasaran, apa yang mencegah mereka menangkap SO dan bagaimana mungkin 1.1(Anda sebutkan dalam komentar Anda)?
M.kazem Akhgary
47

Cara yang benar adalah dengan memperbaiki overflow, tapi ....

Anda dapat memberi diri Anda tumpukan yang lebih besar: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

Anda dapat menggunakan properti System.Diagnostics.StackTrace FrameCount untuk menghitung bingkai yang telah Anda gunakan dan menampilkan pengecualian Anda sendiri saat batas bingkai tercapai.

Atau, Anda dapat menghitung ukuran tumpukan yang tersisa dan memberikan pengecualian Anda sendiri saat berada di bawah ambang batas: -

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

Tangkap kejunya. ;)


sumber
47
Cheesejauh dari spesifik. Saya akan pergi untukthrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Meskipun tidak ada keraguan bahwa Gouda adalah keju yang luar biasa, seharusnya RollingCheeseException ("Double Gloucester") benar-benar melihat cheese-rolling.co.uk
3
lol, 1) memperbaiki tidak mungkin karena tanpa menangkapnya Anda sering tidak tahu di mana itu terjadi 2) meningkatkan Stacksize tidak berguna dengan rekursi tanpa akhir danm 3) memeriksa Stack di lokasi yang benar seperti yang pertama
Firo
2
tapi saya tidak toleran terhadap laktosa
redoc
39

Dari halaman MSDN di StackOverflowException :

Dalam versi sebelumnya dari .NET Framework, aplikasi Anda bisa menangkap objek StackOverflowException (misalnya, untuk memulihkan dari rekursi tak terbatas). Namun, praktik tersebut saat ini tidak disarankan karena kode tambahan yang signifikan diperlukan untuk menangkap pengecualian stack overflow dan melanjutkan eksekusi program secara andal.

Dimulai dengan .NET Framework versi 2.0, objek StackOverflowException tidak dapat ditangkap oleh blok coba-tangkap dan proses terkait diakhiri secara default. Akibatnya, pengguna disarankan untuk menulis kode mereka untuk mendeteksi dan mencegah stack overflow. Misalnya, jika aplikasi Anda bergantung pada rekursi, gunakan penghitung atau kondisi status untuk menghentikan loop rekursif. Perhatikan bahwa aplikasi yang menghosting waktu proses bahasa umum (CLR) dapat menentukan bahwa CLR membongkar domain aplikasi tempat terjadi pengecualian stack overflow dan membiarkan proses terkait berlanjut. Untuk informasi selengkapnya, lihat Antarmuka ICLRPolicyManager dan Menghosting Waktu Proses Bahasa Umum.

Damien_The_Tidak percaya
sumber
23

Seperti yang telah dikatakan beberapa pengguna, Anda tidak dapat menangkap pengecualian. Namun, jika Anda kesulitan mencari tahu di mana itu terjadi, Anda mungkin ingin mengonfigurasi studio visual agar berhenti ketika dilempar.

Untuk melakukan itu, Anda perlu membuka Pengaturan Pengecualian dari menu 'Debug'. Dalam versi Visual Studio yang lebih lama, ini ada di 'Debug' - 'Pengecualian'; dalam versi yang lebih baru, ada di 'Debug' - 'Windows' - 'Exception Settings'.

Setelah Anda membuka pengaturan, perluas 'Common Language Runtime Exception', perluas 'System', gulir ke bawah dan centang 'System.StackOverflowException'. Kemudian Anda dapat melihat tumpukan panggilan dan mencari pola panggilan yang berulang. Itu akan memberi Anda gambaran tentang ke mana harus mencari untuk memperbaiki kode yang menyebabkan tumpukan melimpah.

Simon
sumber
1
Di mana Debug - Pengecualian di VS 2015?
FrenkyB
1
Debug - Windows - Pengaturan Pengecualian
Simon
15

Seperti yang disebutkan di atas beberapa kali, tidak mungkin menangkap StackOverflowException yang dimunculkan oleh Sistem karena status proses yang rusak. Tapi ada cara untuk melihat pengecualian sebagai acara:

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

Dimulai dengan .NETFramework versi 4, peristiwa ini tidak dimunculkan untuk pengecualian yang merusak status proses, seperti tumpukan overflows atau pelanggaran akses, kecuali jika pengendali peristiwa penting keamanan dan memiliki atribut HandleProcessCorruptedStateExceptionsAttribute.

Namun aplikasi Anda akan berhenti setelah keluar dari fungsi-acara (solusi yang SANGAT kotor, adalah memulai ulang aplikasi dalam acara ini haha, belum melakukannya dan tidak akan pernah melakukannya). Tapi itu cukup bagus untuk penebangan!

Di .NETFramework versi 1.0 dan 1.1, pengecualian tidak tertangani yang terjadi di utas selain utas aplikasi utama ditangkap oleh runtime dan oleh karena itu tidak menyebabkan aplikasi dihentikan. Jadi, peristiwa UnhandledException bisa dimunculkan tanpa penghentian aplikasi. Dimulai dengan .NET Framework versi 2.0, penghentian ini untuk pengecualian yang tidak tertangani di utas turunan telah dihapus, karena efek kumulatif dari kegagalan diam tersebut termasuk penurunan kinerja, data yang rusak, dan penguncian, yang semuanya sulit untuk di-debug. Untuk informasi selengkapnya, termasuk daftar kasus di mana runtime tidak berhenti, lihat Pengecualian di Thread Terkelola.

FooBarTheLittle
sumber
6

Ya dari CLR 2.0 stack overflow dianggap sebagai situasi yang tidak dapat dipulihkan. Jadi runtime masih menghentikan proses.

Untuk detailnya silakan lihat dokumentasi http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
sumber
Dari CLR 2.0 a StackOverflowExceptionmenghentikan proses secara default.
Brian Rasmussen
Tidak. Anda dapat menangkap OOM dan dalam beberapa kasus mungkin masuk akal untuk melakukannya. Saya tidak tahu apa yang Anda maksud dengan hilangnya benang. Jika utas memiliki pengecualian yang tidak tertangani, CLR akan menghentikan proses. Jika utas Anda menyelesaikan metodenya, benang itu akan dibersihkan.
Brian Rasmussen
5

Tidak boleh. CLR tidak akan membiarkan Anda. Stack overflow adalah kesalahan fatal dan tidak dapat dipulihkan.

Matthew Scharley
sumber
Jadi, bagaimana Anda membuat Uji Unit gagal untuk pengecualian ini jika alih-alih mudah ditangkap, ia malah membuat uji unit runner?
BrainSlugs83
1
@Bayu_joo Tidak, karena itu ide yang konyol. Mengapa Anda menguji apakah kode Anda gagal dengan StackOverflowException? Apa yang terjadi jika CLR berubah sehingga dapat menangani tumpukan yang lebih dalam? Apa yang terjadi jika Anda memanggil fungsi unit yang diuji di suatu tempat yang sudah memiliki tumpukan bersarang yang dalam? Sepertinya sesuatu yang tidak bisa diuji. Jika Anda mencoba membuangnya secara manual, pilih pengecualian yang lebih baik untuk tugas tersebut.
Matthew Scharley
5

Anda tidak bisa karena sebagian besar postingan menjelaskan, izinkan saya menambahkan area lain:

Di banyak situs web Anda akan menemukan orang-orang mengatakan bahwa cara untuk menghindarinya adalah menggunakan AppDomain yang berbeda jadi jika ini terjadi domain akan dibongkar. Itu benar-benar salah (kecuali Anda meng-host CLR Anda) karena perilaku default CLR akan memunculkan peristiwa KillProcess, menurunkan AppDomain default Anda.

Tidak ada masalah jerami
sumber
3

Itu tidak mungkin, dan untuk alasan yang bagus (untuk satu, pikirkan tentang semua tangkapan (Pengecualian) {} di sekitar).

Jika Anda ingin melanjutkan eksekusi setelah stack overflow, jalankan kode berbahaya di AppDomain yang berbeda. Kebijakan CLR dapat disetel untuk menghentikan AppDomain saat ini saat melimpah tanpa memengaruhi domain asli.

ima
sumber
2
Pernyataan "catch" tidak akan menjadi masalah, karena pada saat pernyataan catch dapat dieksekusi, sistem akan memutar kembali efek dari apa pun yang mencoba menggunakan dua ruang stack yang banyak. Tidak ada alasan menangkap pengecualian stack overflow berbahaya. Alasan mengapa pengecualian semacam itu tidak dapat ditangkap adalah karena memungkinkannya untuk ditangkap dengan aman akan memerlukan penambahan beberapa overhead tambahan ke semua kode yang menggunakan tumpukan, meskipun tidak meluap.
supercat
4
Pada titik tertentu, pernyataan tersebut tidak dipikirkan dengan baik. Jika Anda tidak dapat menangkap Stackoverflow, Anda mungkin tidak akan pernah tahu DI MANA hal itu terjadi dalam lingkungan produksi.
Offler