Warum beendet Environment.Exit () das Programm nicht mehr?

134

Dies ist etwas, das ich erst vor ein paar Tagen entdeckt habe. Ich habe durch diese Frage die Bestätigung erhalten, dass es nicht nur auf meine Maschine beschränkt ist .

Der einfachste Weg, dies zu reproduzieren, besteht darin, eine Windows Forms-Anwendung zu starten, eine Schaltfläche hinzuzufügen und diesen Code zu schreiben:

    private void button1_Click(object sender, EventArgs e) {
        MessageBox.Show("yada");
        Environment.Exit(1);         // Kaboom!
    }

Das Programm schlägt fehl, nachdem die Anweisung Exit () ausgeführt wurde. Unter Windows Forms wird "Fehler beim Erstellen des Fensterhandles" angezeigt.

Durch Aktivieren des nicht verwalteten Debuggens wird deutlich, was los ist. Die COM- Modalschleife wird ausgeführt und ermöglicht die Zustellung einer WM_PAINT-Nachricht. Das ist auf einer entsorgten Form fatal.

Die einzigen Fakten, die ich bisher gesammelt habe, sind:

  • Es ist nicht nur auf die Ausführung mit dem Debugger beschränkt. Dies schlägt auch ohne fehl. Eher schlecht wird der WER-Absturzdialog auch zweimal angezeigt .
  • Es hat nichts mit der Bitterkeit des Prozesses zu tun. Die wow64-Ebene ist ziemlich berüchtigt, aber ein AnyCPU-Build stürzt auf die gleiche Weise ab.
  • Es hat nichts mit der .NET-Version zu tun. 4.5 und 3.5 stürzen auf die gleiche Weise ab.
  • Der Exit-Code spielt keine Rolle.
  • Das Aufrufen von Thread.Sleep () vor dem Aufrufen von Exit () behebt das Problem nicht.
  • Dies geschieht unter der 64-Bit-Version von Windows 8, und Windows 7 scheint nicht in gleicher Weise betroffen zu sein.
  • Dies sollte ein relativ neues Verhalten sein, das habe ich noch nie gesehen. Ich sehe keine relevanten Updates, die über Windows Update bereitgestellt werden , obwohl der Update-Verlauf auf meinem Computer nicht mehr korrekt ist.
  • Dies ist ein grob brechendes Verhalten. Sie würden Code wie diesen in einen Ereignishandler für AppDomain.UnhandledException schreiben, und er stürzt auf die gleiche Weise ab.

Ich bin besonders daran interessiert, was Sie möglicherweise tun könnten, um diesen Absturz zu vermeiden. Besonders das Szenario AppDomain.UnhandledException macht mich fertig; Es gibt nicht viele Möglichkeiten, ein .NET-Programm zu beenden. Beachten Sie, dass das Aufrufen von Application.Exit () oder Form.Close () in einem Ereignishandler für UnhandledException nicht gültig ist und daher keine Problemumgehungen darstellt.


UPDATE: Mehrdad wies darauf hin, dass der Finalizer-Thread Teil des Problems sein könnte. Ich denke, ich sehe dies und sehe auch einige Beweise für das 2-Sekunden-Timeout, dass die CLR dem Finalizer-Thread die Ausführung gibt.

Der Finalizer befindet sich in NativeWindow.ForceExitMessageLoop (). Dort gibt es eine IsWindow () Win32-Funktion, die in etwa der Codeposition entspricht und 0x3c versetzt, wenn der Maschinencode im 32-Bit-Modus betrachtet wird. Es scheint, dass IsWindow () blockiert. Ich kann jedoch keine gute Stapelverfolgung für die Interna erhalten. Der Debugger glaubt, dass der P / Invoke- Aufruf gerade zurückgegeben wurde. Das ist schwer zu erklären. Wenn Sie eine bessere Stapelverfolgung erhalten können, würde ich sie gerne sehen. Bergwerk:

System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.ForceExitMessageLoop() + 0x3c bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Finalize() + 0x16 bytes
[Native to Managed Transition]
kernel32.dll!@BaseThreadInitThunk@12()  + 0xe bytes
ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes

Nichts über dem ForceExitMessageLoop-Aufruf, nicht verwalteter Debugger aktiviert.

Hans Passant
quelle
2
Ich habe dies gerade mit .NET 4, 4 Client Profile, 3.5, 3.5 Client Profile, 3.0 und 2.0 versucht und bei keinem von ihnen einen Fehler erhalten. 64-Bit-Windows 7 ist mein Betriebssystem mit VS2010.
Steve
2
@ Steve This happens on the 64-bit version of Windows 8Hans hat es gesagt!
Parimal Raj
7
Ich kann dies wiederholen (Win 8, 64-Bit), Ihren Code kopieren / einfügen und eine Schaltfläche verkabeln, und ich erhalte die genauen beschriebenen Symptome.
TastaturP
3
Eine Konsolenmodus-App konnte dieses Problem nicht demonstrieren. Es kann nichts schief gehen, wenn Exit () weiterhin Nachrichten pumpt.
Hans Passant
3
Ich habe diese Art von Verhalten Exit(0)vor einiger Zeit mit 64-Bit-Win7, Ändern, festgestelltExitCode hat jetzt nicht geholfen, es Process.GetCurrentProcess().Kill()ohne Probleme zu verwenden
Sriram Sakthivel

Antworten:

85

Ich habe Microsoft wegen dieses Problems kontaktiert und das schien sich ausgezahlt zu haben. Zumindest würde ich gerne glauben, dass es so ist :). Obwohl ich keine Bestätigung einer Lösung von ihnen erhalten habe, ist es schwierig, die Windows-Gruppe direkt zu kontaktieren, und ich musste einen Vermittler verwenden.

Ein über Windows Update bereitgestelltes Update hat das Problem behoben. Die spürbare Verzögerung von 2 Sekunden vor dem Absturz ist nicht mehr vorhanden, was stark darauf hindeutet, dass der IsWindow () - Deadlock behoben wurde. Und das Programm fährt sauber und zuverlässig herunter. Das Update installierte Patches für Windows Defender, wdboot.sys, wdfilter.sys, tcpip.sys, rpcrt4.dll, uxtheme.dll, crypt32.dll und wintrust.dll

Uxtheme.dll ist die ungerade Ente. Es implementiert die Visual Styles-Themen-API und wird von diesem Testprogramm verwendet. Ich kann nicht sicher sein, aber mein Geld ist dafür die Ursache des Problems. Die Kopie in C: \ WINDOWS \ system32 hat die Versionsnummer 6.2.9200.16660, die am 14. August 2013 auf meinem Computer erstellt wurde.

Fall abgeschlossen.

Hans Passant
quelle
11
Der Windows Update-Verlauf ist auf meinem Computer nicht mehr korrekt. Ich weiß nur, dass es am 14. August installiert wurde.
Hans Passant
51

Ich weiß nicht, warum es "nicht mehr" funktioniert , aber ich denke, es werden Environment.Exitausstehende Finalizer ausgeführt. Environment.FailFastnicht.

Es kann sein, dass Sie (aus irgendeinem bizarren Grund) seltsame ausstehende Finalizer haben, die anschließend ausgeführt werden müssen, wodurch dies geschieht.

user541686
quelle
2
Sie könnten auf etwas sein. Der Finalizer führt NativeWindow.ForceExitMessageLoop () aus. Seltsamerweise ist es in keinem Aufruf verschachtelt.
Hans Passant
@ HansPassant: Ich wünschte, ich könnte das Problem wiederholen, damit ich es untersuchen könnte, aber ich kann nicht. Ist der Aufruf, NativeWindow.ForceExitMessageLoopin verwaltetem oder nicht verwaltetem Code zu stecken? Steckt es überhaupt fest oder wartet es auf eine Nachricht oder etwas anderes?
user541686
Dies scheint sicherlich auf das Kernproblem hinzuweisen. Ich denke, es ist die Winapi-Funktion IsWindow (), die die Ursache des Problems ist. Ich denke, ich sehe auch das 2-Sekunden-Timeout im Finalizer-Thread, nach dem alles zur Hölle geht. Der Debugger zeigt nicht an , dass er den IsWindow () -Aufruf ausführt, aber ich habe gesehen, wie Windows zuvor mit dem Stack Streiche gespielt und ihn ausgeschaltet hat, wenn kritischer Code in Windows eingegeben wurde.
Hans Passant
4
Ich denke, dass die Environment.FailFast () -Methode für den gegebenen Fall nicht behandelter Ausnahmen wahrscheinlich die beste Methode ist, die man sowieso verwenden kann. (Ich war mir dessen nicht bewusst - danke!) Es gibt jedoch eine Menge Legacy-Code, der Environment.Exit () verwenden würde, der leider unangenehm abstürzen wird :(
Ian Yates
2
Sie sind definitiv auf etwas. In meinem Fall hatte ich einen IHost mit IHost.StartAsync gestartet, um einige Integrationstests durchzuführen, und dennoch wurde der Prozess nach dem Aufruf (und natürlich dem Warten auf) IHost.StopAsync immer noch nicht beendet. Erst nach dem Aufruf von IHost.Dispose wird der Prozess beendet. Vielen Dank für den Tipp
Malte R
6

Dies erklärt nicht, warum es passiert, aber ich würde keinen Environment.ExitButton-Event-Handler wie Ihr Beispiel aufrufen - schließen Sie stattdessen das Hauptformular, wie in Renes Antwort vorgeschlagen .

Was einen AppDomain.UnhandledExceptionHandler betrifft, könnten Sie vielleicht einfach einstellen, Environment.ExitCodeanstatt anzurufen Environment.Exit.

Ich bin mir nicht sicher, was Sie hier erreichen wollen. Warum möchten Sie einen Exit-Code aus einer Windows Forms-Anwendung zurückgeben? Normalerweise werden Exit-Codes von Konsolenanwendungen verwendet.

Ich bin besonders daran interessiert, was Sie möglicherweise tun können, um diesen Absturz zu vermeiden. Calling Environment.Exit () ist erforderlich, um zu verhindern, dass der WER-Dialog angezeigt wird.

Haben Sie einen Versuch / Fang in der Hauptmethode? Für Windows Forms-Anwendungen habe ich immer einen Versuch / Fang um die Nachrichtenschleife sowie die nicht behandelten Ausnahmebehandlungsroutinen.

Joe
quelle
Ich bin mir ziemlich sicher, dass du Application.Exitstattdessen anrufen sollst Environment.Exit.
user541686
7
Dies ist leider keine Problemumgehung. Das Aufrufen von Environment.Exit () ist erforderlich, um zu verhindern, dass der WER-Dialog angezeigt wird. Beachten Sie auch die "bekannte Tatsache", der Exit-Code spielt keine Rolle.
Hans Passant
7
@Hans: Fängt AppDomain.UnhandledException ab, um zu versuchen, den WER-Dialog überhaupt zu vermeiden? Ich meine, wenn es eine nicht behandelte Ausnahme ist, wird der Dialog WER soll zeigen, ist es nicht?
Harry Johnston
2

Ich habe das gleiche Problem in unserer App gefunden, wir haben es mit dem folgenden Konstrukt gelöst:

Environment.ExitCode=1;
Application.Exit();
Jesse
quelle