C # fängt eine Stapelüberlaufausnahme ab

115

Ich habe einen rekursiven Aufruf einer Methode, die eine Stapelüberlaufausnahme auslöst. Der erste Aufruf ist von einem Try-Catch-Block umgeben, die Ausnahme wird jedoch nicht abgefangen.

Verhält sich die Stapelüberlaufausnahme auf besondere Weise? Kann ich die Ausnahme richtig abfangen / behandeln?

Nicht sicher, ob relevant, aber zusätzliche Informationen:

  • Die Ausnahme wird nicht im Hauptthread ausgelöst

  • Das Objekt, in dem der Code die Ausnahme auslöst, wird manuell von Assembly.LoadFrom (...) geladen. CreateInstance (...)

Toto
quelle
3
@ RichardOD, sicher, ich behebe den Fehler, weil es ein Fehler war. Das Problem kann jedoch anders aussehen und ich möchte damit umgehen
Toto
7
Einverstanden ist ein Stapelüberlauf ein schwerwiegender Fehler, der nicht abgefangen werden kann, da er nicht abgefangen werden sollte. Korrigieren Sie stattdessen den fehlerhaften Code.
Ian Kemp
11
@RichardOD: Wenn man zB einen Parser mit rekursivem Abstieg entwerfen und der Tiefe keine künstlichen Grenzen auferlegen möchte, die über die tatsächlich vom Host-Computer geforderten Grenzen hinausgehen, wie sollte man vorgehen? Wenn ich meine Druthers hätte, gäbe es eine StackCritical-Ausnahme, die explizit abgefangen werden könnte und die ausgelöst würde, solange noch ein wenig Stapelplatz übrig wäre. es würde sich selbst deaktivieren, bis es tatsächlich geworfen wurde, und konnte dann nicht gefangen werden, bis eine sichere Menge an Stapelplatz übrig blieb.
Supercat
2
Diese Frage ist nützlich - ich möchte einen Komponententest nicht bestehen, wenn eine Stapelüberlaufausnahme auftritt - aber NUnit verschiebt den Test nur in die Kategorie "ignoriert", anstatt ihn wie bei anderen Ausnahmen zu bestehen - ich muss ihn abfangen und mache Assert.Failstattdessen eine. So ernst - wie gehen wir vor?
BrainSlugs83

Antworten:

109

Ab 2.0 kann eine StackOverflow-Ausnahme nur unter den folgenden Umständen abgefangen werden.

  1. Die CLR wird in einer gehosteten Umgebung * ausgeführt, in der der Host speziell die Behandlung von StackOverflow-Ausnahmen zulässt
  2. Die Stackoverflow-Ausnahme wird vom Benutzercode ausgelöst und ist nicht auf eine tatsächliche Stack-Overflow-Situation zurückzuführen ( Referenz ).

* "Gehostete Umgebung" wie in "Mein Code hostet CLR und ich konfiguriere die CLR-Optionen" und nicht "Mein Code läuft auf Shared Hosting".

JaredPar
quelle
27
Warum ist das StackoverflowException-Objekt vorhanden, wenn es in keinem relevanten Scebario abgefangen werden kann?
Manu
9
@ Manu aus mindestens ein paar Gründen. 1) Ist es, dass es irgendwie in 1.1 gefangen werden könnte und daher einen Zweck hatte. 2) Es kann immer noch abgefangen werden, wenn Sie die CLR hosten, so dass es immer noch ein gültiger Ausnahmetyp ist
JaredPar
3
Wenn es nicht abgefangen werden kann ... Warum enthält das Windows-Ereignis, das erklärt, was passiert ist, standardmäßig nicht die vollständige Stapelverfolgung?
10
Wie kann man zulassen, dass StackOverflowExceptions in einer gehosteten Umgebung behandelt werden? Der Grund, den ich frage, ist, dass ich eine gehostete Umgebung betreibe und genau dieses Problem habe, bei dem der gesamte App-Pool zerstört wird. Ich würde es viel lieber haben, wenn es den Thread abbricht, wo es sich wieder nach oben abwickeln kann, und ich kann dann den Fehler protokollieren und fortfahren, ohne dass alle Threads des Apppools getötet werden.
Brain2000
Starting with 2.0 ...Ich bin neugierig, was hindert sie daran, SO zu fangen und wie war es möglich 1.1(das haben Sie in Ihrem Kommentar erwähnt)?
M. Kazem Akhgary 24.
47

Der richtige Weg ist, den Überlauf zu beheben, aber ....

Sie können sich einen größeren Stapel geben: -

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

Mit der System.Diagnostics.StackTrace FrameCount-Eigenschaft können Sie die von Ihnen verwendeten Frames zählen und Ihre eigene Ausnahme auslösen, wenn ein Frame-Limit erreicht ist.

Sie können auch die Größe des verbleibenden Stapels berechnen und Ihre eigene Ausnahme auslösen, wenn dieser unter einen Schwellenwert fällt: -

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

Fang einfach den Käse. ;)


quelle
47
Cheeseist alles andere als spezifisch. Ich würde gehen fürthrow new CheeseException("Gouda");
C.Evenhuis
13
@ C.Evenhuis Obwohl es keinen Zweifel gibt, dass Gouda ein außergewöhnlicher Käse ist, sollte es eine RollingCheeseException ("Double Gloucester") sein, siehe Cheese-rolling.co.uk
3
lol, 1) das Reparieren ist nicht möglich, weil man oft nicht weiß, wo es passiert, ohne es zu fangen. 2) Das Erhöhen der Stapelgröße ist mit endloser Rekursion nutzlos. 3) Das Überprüfen des Stapels an der richtigen Stelle ist wie beim ersten
Firo
2
aber ich bin laktoseintolerant
redoc
39

Auf der MSDN-Seite von StackOverflowException s:

In früheren Versionen von .NET Framework konnte Ihre Anwendung ein StackOverflowException-Objekt abfangen (z. B. um eine unbegrenzte Rekursion wiederherzustellen). Von dieser Vorgehensweise wird derzeit jedoch abgeraten, da erheblicher zusätzlicher Code erforderlich ist, um eine Stapelüberlaufausnahme zuverlässig abzufangen und die Programmausführung fortzusetzen.

Ab .NET Framework Version 2.0 kann ein StackOverflowException-Objekt nicht mehr von einem Try-Catch-Block abgefangen werden, und der entsprechende Prozess wird standardmäßig beendet. Folglich wird Benutzern empfohlen, ihren Code zu schreiben, um einen Stapelüberlauf zu erkennen und zu verhindern. Wenn Ihre Anwendung beispielsweise von einer Rekursion abhängt, verwenden Sie einen Zähler oder eine Statusbedingung, um die rekursive Schleife zu beenden. Beachten Sie, dass eine Anwendung, die die Common Language Runtime (CLR) hostet, angeben kann, dass die CLR die Anwendungsdomäne entlädt, in der die Stapelüberlaufausnahme auftritt, und den entsprechenden Prozess fortsetzen kann. Weitere Informationen finden Sie unter ICLRPolicyManager-Schnittstelle und Hosting der Common Language Runtime.

Damien_The_Unbeliever
quelle
23

Wie bereits mehrere Benutzer gesagt haben, können Sie die Ausnahme nicht abfangen. Wenn Sie jedoch Schwierigkeiten haben, herauszufinden, wo es passiert, möchten Sie Visual Studio möglicherweise so konfigurieren, dass es beim Werfen nicht funktioniert.

Dazu müssen Sie die Ausnahmeeinstellungen im Menü "Debuggen" öffnen. In älteren Versionen von Visual Studio befindet sich dies unter "Debug" - "Ausnahmen". In neueren Versionen ist es bei 'Debug' - 'Windows' - 'Ausnahmeeinstellungen'.

Wenn Sie die Einstellungen geöffnet haben, erweitern Sie 'Common Language Runtime Exceptions', erweitern Sie 'System', scrollen Sie nach unten und aktivieren Sie 'System.StackOverflowException'. Dann können Sie sich den Aufrufstapel ansehen und nach dem sich wiederholenden Muster von Anrufen suchen. Das sollte Ihnen eine Vorstellung davon geben, wo Sie suchen müssen, um den Code zu beheben, der den Stapelüberlauf verursacht.

Simon
quelle
1
Wo ist Debug - Ausnahmen in VS 2015?
FrenkyB
1
Debug - Windows - Ausnahmeeinstellungen
Simon
15

Wie oben mehrmals erwähnt, ist es nicht möglich, eine StackOverflowException abzufangen, die vom System aufgrund eines beschädigten Prozessstatus ausgelöst wurde. Es gibt jedoch eine Möglichkeit, die Ausnahme als Ereignis zu erkennen:

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

Ab .NET Framework Version 4 wird dieses Ereignis nicht für Ausnahmen ausgelöst, die den Status des Prozesses beschädigen, z. B. Stapelüberläufe oder Zugriffsverletzungen, es sei denn, der Ereignishandler ist sicherheitskritisch und verfügt über das Attribut HandleProcessCorruptedStateExceptionsAttribute.

Trotzdem wird Ihre Anwendung nach dem Beenden der Ereignisfunktion beendet (eine SEHR schmutzige Problemumgehung bestand darin, die App innerhalb dieses Ereignisses neu zu starten, haha, habe dies nicht getan und werde es niemals tun). Aber es ist gut genug für die Protokollierung!

In den .NET Framework-Versionen 1.0 und 1.1 wird eine nicht behandelte Ausnahme, die in einem anderen Thread als dem Hauptanwendungsthread auftritt, von der Laufzeit abgefangen und führt daher nicht zum Beenden der Anwendung. Somit ist es möglich, dass das UnhandledException-Ereignis ausgelöst wird, ohne dass die Anwendung beendet wird. Ab der .NET Framework-Version 2.0 wurde dieser Backstop für nicht behandelte Ausnahmen in untergeordneten Threads entfernt, da der kumulative Effekt solcher stillen Fehler Leistungseinbußen, beschädigte Daten und Abstürze beinhaltete, die alle schwer zu debuggen waren. Weitere Informationen, einschließlich einer Liste von Fällen, in denen die Laufzeit nicht beendet wird, finden Sie unter Ausnahmen in verwalteten Threads.

FooBarTheLittle
quelle
6

Ja, der CLR 2.0-Stapelüberlauf wird als nicht wiederherstellbare Situation angesehen. Die Laufzeit hat den Prozess also immer noch heruntergefahren.

Weitere Informationen finden Sie in der Dokumentation unter http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx

Brian Rasmussen
quelle
Ab CLR 2.0 StackOverflowExceptionbeendet a den Prozess standardmäßig.
Brian Rasmussen
Nein. Sie können OOM fangen und in einigen Fällen kann es sinnvoll sein, dies zu tun. Ich weiß nicht, was du mit dem Verschwinden von Fäden meinst. Wenn ein Thread eine nicht behandelte Ausnahme hat, beendet die CLR den Prozess. Wenn Ihr Thread seine Methode abgeschlossen hat, wird er bereinigt.
Brian Rasmussen
5

Das kannst du nicht. Die CLR lässt Sie nicht. Ein Stapelüberlauf ist ein schwerwiegender Fehler und kann nicht behoben werden.

Matthew Scharley
quelle
Wie kann ein Unit-Test für diese Ausnahme fehlschlagen, wenn der Unit-Test-Runner nicht abfangbar ist, sondern abstürzt?
BrainSlugs83
1
@ BrainSlugs83. Sie nicht, weil das eine dumme Idee ist. Warum testen Sie, ob Ihr Code mit einer StackOverflowException trotzdem fehlschlägt? Was passiert, wenn sich die CLR ändert, damit sie einen tieferen Stapel verarbeiten kann? Was passiert, wenn Sie Ihre Unit-Tested-Funktion an einem Ort aufrufen, der bereits einen tief verschachtelten Stapel hat? Es scheint etwas zu sein, das nicht getestet werden kann. Wenn Sie versuchen, es manuell zu werfen, wählen Sie eine bessere Ausnahme für die Aufgabe.
Matthew Scharley
5

Sie können nicht, da die meisten Beiträge erklären, lassen Sie mich einen weiteren Bereich hinzufügen:

Auf vielen Websites finden Sie Leute, die sagen, dass die Möglichkeit, dies zu vermeiden, die Verwendung einer anderen AppDomain ist. In diesem Fall wird die Domain entladen. Das ist absolut falsch (es sei denn, Sie hosten Ihre CLR), da das Standardverhalten der CLR ein KillProcess-Ereignis auslöst und Ihre Standard-AppDomain herunterfährt.

Kein Heu Problema
quelle
3

Es ist unmöglich und aus gutem Grund (denken Sie zum einen an all diese Fänge (Ausnahme) {} herum).

Wenn Sie die Ausführung nach einem Stapelüberlauf fortsetzen möchten, führen Sie gefährlichen Code in einer anderen AppDomain aus. CLR-Richtlinien können so eingestellt werden, dass die aktuelle AppDomain bei Überlauf beendet wird, ohne dass dies Auswirkungen auf die ursprüngliche Domäne hat.

ima
quelle
2
Die "catch" -Anweisungen wären nicht wirklich ein Problem, da das System zu dem Zeitpunkt, zu dem eine catch-Anweisung ausgeführt werden könnte, die Auswirkungen dessen zurückgesetzt hätte, was versucht hatte, zwei viel Stapelspeicherplatz zu nutzen. Es gibt keinen Grund, warum das Abfangen von Stapelüberlaufausnahmen gefährlich sein müsste. Der Grund, warum solche Ausnahmen nicht abgefangen werden können, besteht darin, dass für das sichere Abfangen des gesamten Codes, der den Stack verwendet, zusätzlicher Aufwand erforderlich ist , auch wenn dieser nicht überläuft.
Supercat
4
Irgendwann ist die Aussage nicht mehr gut durchdacht. Wenn Sie den Stackoverflow nicht abfangen können, wissen Sie möglicherweise nie, wo er in einer Produktionsumgebung passiert ist.
Offler